/*-------------------------------------------------------------------------
 *
 * parse_utilcmd.c
 *	  执行各种实用命令的解析分析工作
 *
 * 以前我们是在 analyze.c 中的 parse_analyze_*() 函数期间完成这项工作。不过
 * 在存在查询树缓存的情况下，这是相当不安全的，因为在执行实用命令时，我们
 * 依赖的任何数据库状态可能在那时已经过时；而实用命令并没有基础设施来
 * 持有锁或重新检查计划有效性。因此这些函数现在在各自实用命令的执行
 * 开始时被调用。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	src/backend/parser/parse_utilcmd.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/amapi.h"
#include "access/htup_details.h"
#include "access/relation.h"
#include "access/reloptions.h"
#include "access/table.h"
#include "access/toast_compression.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_type.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parser.h"
#include "rewrite/rewriteManip.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"


/* transformCreateStmt 和其子例程共享的状态 */
typedef struct
{
	ParseState *pstate;			/* 整体解析器状态 */
	const char *stmtType;		/* "CREATE [FOREIGN] TABLE" 或 "ALTER TABLE" */
	RangeVar   *relation;		/* 要创建的关系 */
	Relation	rel;			/* 如果是 ALTER，则已打开/锁定的关系 */
	List	   *inhRelations;	/* 要继承的关系 */
	bool		isforeign;		/* 如果为 CREATE/ALTER FOREIGN TABLE 则为真 */
	bool		isalter;		/* 如果正在修改现有表则为真 */
	List	   *columns;		/* ColumnDef 项 */
	List	   *ckconstraints;	/* CHECK约束 */  
	List	   *fkconstraints;	/* FOREIGN KEY 约束 */
	List	   *ixconstraints;	/* 创建索引的约束 */
	List	   *likeclauses;	/* 需要后处理的 LIKE 子句 */
	List	   *blist;			/* 创建表之前要完成的“前置清单” */
	List	   *alist;			/* 创建表后要完成的“后置清单” */
	IndexStmt  *pkey;			/* PRIMARY KEY 索引（如果有） */
	bool		ispartitioned;	/* 如果表被分区则为真 */
	PartitionBoundSpec *partbound;	/* 转换的 FOR VALUES */
	bool		ofType;			/* 如果语句包含 typename 的 OF 则为真 */
} CreateStmtContext;

/* transformCreateSchemaStmtElements 和其子例程共享的状态 */
typedef struct
{
	const char *schemaname;		/* 架构的名称 */
	List	   *sequences;		/* CREATE SEQUENCE 项 */
	List	   *tables;			/* CREATE TABLE 项 */
	List	   *views;			/* CREATE VIEW 项 */
	List	   *indexes;		/* CREATE INDEX 项目 */
	List	   *triggers;		 
/* CREATE TRIGGER items */
	List	   *grants;			/* GRANT items */
} CreateSchemaStmtContext;


static void fc_transformColumnDefinition(CreateStmtContext *fc_cxt,
									  ColumnDef *fc_column);
static void fc_transformTableConstraint(CreateStmtContext *fc_cxt,
									 Constraint *fc_constraint);
static void fc_transformTableLikeClause(CreateStmtContext *fc_cxt,
									 TableLikeClause *fc_table_like_clause);
static void fc_transformOfType(CreateStmtContext *fc_cxt,
							TypeName *fc_ofTypename);
static CreateStatsStmt *fc_generateClonedExtStatsStmt(RangeVar *fc_heapRel,
												   Oid fc_heapRelid,
												   Oid fc_source_statsid,
												   const AttrMap *fc_attmap);
static List *get_collation(Oid fc_collation, Oid fc_actual_datatype);
static List *get_opclass(Oid fc_opclass, Oid fc_actual_datatype);
static void fc_transformIndexConstraints(CreateStmtContext *fc_cxt);
static IndexStmt *fc_transformIndexConstraint(Constraint *fc_constraint,
										   CreateStmtContext *fc_cxt);
static void fc_transformFKConstraints(CreateStmtContext *fc_cxt,
								   bool fc_skipValidation,
								   bool fc_isAddConstraint);
static void fc_transformCheckConstraints(CreateStmtContext *fc_cxt,
									  bool fc_skipValidation);
static void fc_transformConstraintAttrs(CreateStmtContext *fc_cxt,
									 List *fc_constraintList);
static void fc_transformColumnType(CreateStmtContext *fc_cxt, ColumnDef *fc_column);
static void fc_setSchemaName(const char *fc_context_schema, char **fc_stmt_schema_name);
static void fc_transformPartitionCmd(CreateStmtContext *fc_cxt, PartitionCmd *fc_cmd);
static List *fc_transformPartitionRangeBounds(ParseState *fc_pstate, List *fc_blist,
										   Relation fc_parent);
static void fc_validateInfiniteBounds(ParseState *fc_pstate, List *fc_blist);
static Const *fc_transformPartitionBoundValue(ParseState *fc_pstate, Node *fc_con,
										   const char *fc_colName, Oid fc_colType, int32 fc_colTypmod,
										   Oid fc_partCollation);


/*
 * transformCreateStmt -
 *	  解析 CREATE TABLE 语句的分析
 *
 * 返回一个按顺序执行的实用命令列表。其中一个将是转换后的 CreateStmt，但在实际的 DefineRelation() 调用之前和之后可能还有额外的操作要执行。
 * 除了正常的实用命令，例如 AlterTableStmt 和 IndexStmt，结果列表可能包含 TableLikeClause(s)，表示需要在 DefineRelation() 之后执行额外的解析分析。
 *
 * SQL 允许约束分散在各处，因此对列进行浏览并收集所有约束到一个地方。
 * 如果有任何隐式索引（例如 UNIQUE 或 PRIMARY KEY），则将其展开为多个 IndexStmt 块。
 *	  - thomas 1997-12-02
 */
List * transformCreateStmt(CreateStmt *fc_stmt, const char *fc_queryString)
{
	ParseState *fc_pstate;
	CreateStmtContext fc_cxt;
	List	   *fc_result;
	List	   *fc_save_alist;
	ListCell   *fc_elements;
	Oid			fc_namespaceid;
	Oid			fc_existing_relid;
	ParseCallbackState fc_pcbstate;

	/* 设置 pstate */
	fc_pstate = make_parsestate(NULL);
	fc_pstate->p_sourcetext = fc_queryString;

	/*
	 * 查找创建命名空间。这也检查目标命名空间的权限，锁定以防止并发删除，检查该命名空间中是否已存在同名关系，并在选择的命名空间是临时时更新 stmt->relation->relpersistence。
	 */
	setup_parser_errposition_callback(&fc_pcbstate, fc_pstate,
									  fc_stmt->relation->location);
	fc_namespaceid =
		RangeVarGetAndCheckCreationNamespace(fc_stmt->relation, NoLock,
											 &fc_existing_relid);
	cancel_parser_errposition_callback(&fc_pcbstate);

	/*
	 * 如果关系已经存在，并且用户指定了 "IF NOT EXISTS"，则退出并发出 NOTICE。
	 */
	if (fc_stmt->if_not_exists && OidIsValid(fc_existing_relid))
	{
		/*
		 * 如果我们在扩展脚本中，则坚持要求预先存在的对象为扩展的成员，以避免安全风险。
		 */
		ObjectAddress fc_address;

		ObjectAddressSet(fc_address, RelationRelationId, fc_existing_relid);
		checkMembershipInCurrentExtension(&fc_address);

		/* 可以跳过 */
		ereport(NOTICE,
				(errcode(ERRCODE_DUPLICATE_TABLE),
				 errmsg("relation \"%s\" already exists, skipping",
						fc_stmt->relation->relname)));
		return NIL;
	}

	/*
	 * 如果目标关系名称不是模式限定的，则进行模式限定。这防止了一些边缘案例，其中附加的重写命令可能会认为它们应该应用于其他名称相同且在搜索路径中更早的关系。但本地临时表实际上指定位于 pg_temp，因此在这种情况下不需要任何额外内容。
	 */
	if (fc_stmt->relation->schemaname == NULL
		&& fc_stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
		fc_stmt->relation->schemaname = get_namespace_name(fc_namespaceid);

	/* 设置 CreateStmtContext */
	fc_cxt.pstate = fc_pstate;
	if (IsA(fc_stmt, CreateForeignTableStmt))
	{
		fc_cxt.stmtType = "CREATE FOREIGN TABLE";
		fc_cxt.isforeign = true;
	}
	else
	{
		fc_cxt.stmtType = "CREATE TABLE";
		fc_cxt.isforeign = false;
	}
	fc_cxt.relation = fc_stmt->relation;
	fc_cxt.rel = NULL;
	fc_cxt.inhRelations = fc_stmt->inhRelations;
	fc_cxt.isalter = false;
	fc_cxt.columns = NIL;
	fc_cxt.ckconstraints = NIL;
	fc_cxt.fkconstraints = NIL;
	fc_cxt.ixconstraints = NIL;
	fc_cxt.likeclauses = NIL;
	fc_cxt.blist = NIL;
	fc_cxt.alist = NIL;
	fc_cxt.pkey = NULL;
	fc_cxt.ispartitioned = fc_stmt->partspec != NULL;
	fc_cxt.partbound = fc_stmt->partbound;
	fc_cxt.ofType = (fc_stmt->ofTypename != NULL);

	Assert(!fc_stmt->ofTypename || !fc_stmt->inhRelations);	/* 语法强制执行 */

	if (fc_stmt->ofTypename)
		fc_transformOfType(&fc_cxt, fc_stmt->ofTypename);

	if (fc_stmt->partspec)
	{
		if (fc_stmt->inhRelations && !fc_stmt->partbound)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("cannot create partitioned table as inheritance child")));
	}

	/*
	 * 遍历表创建子句中的每个主要元素。将列定义与约束分开，并进行初步分析。
	 */
	foreach(fc_elements, fc_stmt->tableElts)
	{
		Node	   *fc_element = lfirst(fc_elements);

		switch (nodeTag(fc_element))
		{
			case T_ColumnDef:
				fc_transformColumnDefinition(&fc_cxt, (ColumnDef *) fc_element);
				break;

			case T_Constraint:
				fc_transformTableConstraint(&fc_cxt, (Constraint *) fc_element);
				break;

			case T_TableLikeClause:
				fc_transformTableLikeClause(&fc_cxt, (TableLikeClause *) fc_element);
				break;

			default:
				elog(ERROR, "unrecognized node type: %d",
					 (int) nodeTag(fc_element));
				break;
		}
	}

	/*
	 * 将我们已经在 cxt.alist 中的任何内容转移到 save_alist，以保持其与 transformIndexConstraints 输出分开。（这可能不再是必要的，但我们会继续这样做以保持 alist 命令的历史执行顺序。）
	 */
	fc_save_alist = fc_cxt.alist;
	fc_cxt.alist = NIL;

	Assert(fc_stmt->constraints == NIL);

	/*
	 * 后处理导致索引定义的约束。
	 */
	fc_transformIndexConstraints(&fc_cxt);

	/*
	 * LIKE 子句的重新考虑应发生在索引创建之后，但在外键创建之前。此顺序至关重要，因为 LIKE 子句可能尝试创建主键。如果主 CREATE TABLE 列表中还有一个 pkey，则创建该 pkey 将不会在运行时检查重复项（因为 index_check_primary_key() 期望我们在这里拒绝重复项）。LIKE 生成的 pkey 的创建行为类似于 ALTER TABLE ADD，因此它将进行检查，但显然只有在其发生在第二时才有效。另一方面，我们希望在外键约束之前创建 pkeys，以防用户尝试创建自引用 FK。
	 */
	fc_cxt.alist = list_concat(fc_cxt.alist, fc_cxt.likeclauses);

	/*
	 * 后处理外键约束。
	 */
	fc_transformFKConstraints(&fc_cxt, true, false);

	/*
	 * 后处理检查约束。
	 *
	 * 对于常规表，所有约束都可以立即标记为有效，因为该表是新的，因此为空。但对于外部表则不然。
	 */
	fc_transformCheckConstraints(&fc_cxt, !fc_cxt.isforeign);

	/*
	 * 输出结果。
	 */
	fc_stmt->tableElts = fc_cxt.columns;
	fc_stmt->constraints = fc_cxt.ckconstraints;

	fc_result = lappend(fc_cxt.blist, fc_stmt);
	fc_result = list_concat(fc_result, fc_cxt.alist);
	fc_result = list_concat(fc_result, fc_save_alist);

	return fc_result;
}

/*
 * generateSerialExtraStmts
 *		生成 CREATE SEQUENCE 和 ALTER SEQUENCE ... OWNED BY 语句
 *		以为序列或身份列创建序列。
 *
 * 这包括确定序列将具有的名称。调用者可以通过传递非空指针给 snamespace_p 和 sname_p 请求返回名称组件。
 */
static void fc_generateSerialExtraStmts(CreateStmtContext *fc_cxt, ColumnDef *fc_column,
						 Oid fc_seqtypid, List *fc_seqoptions,
						 bool fc_for_identity, bool fc_col_exists,
						 char **fc_snamespace_p, char **fc_sname_p)
{
	ListCell   *fc_option;
	DefElem    *fc_nameEl = NULL;
	DefElem    *fc_loggedEl = NULL;
	Oid			fc_snamespaceid;
	char	   *fc_snamespace;
	char	   *fc_sname;
	char		fc_seqpersistence;
	CreateSeqStmt *fc_seqstmt;
	AlterSeqStmt *fc_altseqstmt;
	List	   *fc_attnamelist;

	/* 复制一份，因为我们最终可能会在下面的代码中修改它 */
	fc_seqoptions = list_copy(fc_seqoptions);

	/*
	 * 检查非 SQL 标准选项（在 CREATE SEQUENCE 内不支持，因为它们是多余的），如果找到将其从 seqoptions 列表中删除。
	 */
	foreach(fc_option, fc_seqoptions)
	{
		DefElem    *fc_defel = lfirst_node(DefElem, fc_option);

		if (strcmp(fc_defel->defname, "sequence_name") == 0)
		{
			if (fc_nameEl)
				errorConflictingDefElem(fc_defel, fc_cxt->pstate);
			fc_nameEl = fc_defel;
			fc_seqoptions = foreach_delete_current(fc_seqoptions, fc_option);
		}
		else if (strcmp(fc_defel->defname, "logged") == 0 ||
				 strcmp(fc_defel->defname, "unlogged") == 0)
		{
			if (fc_loggedEl)
				errorConflictingDefElem(fc_defel, fc_cxt->pstate);
			fc_loggedEl = fc_defel;
			fc_seqoptions = foreach_delete_current(fc_seqoptions, fc_option);
		}
	}

	/*
	 * 确定用于序列的命名空间和名称。
	 */
	if (fc_nameEl)
	{
		/* 使用指定名称 */
		RangeVar   *fc_rv = makeRangeVarFromNameList(castNode(List, fc_nameEl->arg));

		fc_snamespace = fc_rv->schemaname;
		if (!fc_snamespace)
		{
			/* 给定未限定的 SEQUENCE NAME，选择命名空间 */
			if (fc_cxt->rel)
				fc_snamespaceid = RelationGetNamespace(fc_cxt->rel);
			else
				fc_snamespaceid = RangeVarGetCreationNamespace(fc_cxt->relation);
			fc_snamespace = get_namespace_name(fc_snamespaceid);
		}
		fc_sname = fc_rv->relname;
	}
	else
	{
		/*
		 * 生成一个名称。
		 *
		 * 尽管我们使用 ChooseRelationName，但不能保证所选的序列名称不会冲突；考虑到足够长的字段名，同一表中的两个不同序列列可能会被分配相同的序列名称，而我们也不会注意到，因为我们尚未创建该序列。在实践中，这似乎很不可能成为问题，特别是因为很少有人需要在一张表中有两个序列列。
		 */
		if (fc_cxt->rel)
			fc_snamespaceid = RelationGetNamespace(fc_cxt->rel);
		else
		{
			fc_snamespaceid = RangeVarGetCreationNamespace(fc_cxt->relation);
			RangeVarAdjustRelationPersistence(fc_cxt->relation, fc_snamespaceid);
		}
		fc_snamespace = get_namespace_name(fc_snamespaceid);
		fc_sname = ChooseRelationName(fc_cxt->relation->relname,
								   fc_column->colname,
								   "seq",
								   fc_snamespaceid,
								   false);
	}

	ereport(DEBUG1,
			(errmsg_internal("%s will create implicit sequence \"%s\" for serial column \"%s.%s\"",
							 fc_cxt->stmtType, fc_sname,
							 fc_cxt->relation->relname, fc_column->colname)));

	 

/*
	 * 确定序列的持久性。默认情况下，我们复制表的持久性，但如果指定了 LOGGED 或 UNLOGGED，则使用它（只要表不是 TEMP）。
	 *
	 * 对于 CREATE TABLE，我们从 cxt->relation 获取持久性，它来自正在进行的 CreateStmt。对于 ALTER TABLE，解析器不会设置 cxt->relation->relpersistence，但我们有 cxt->rel 作为现有表，因此我们从那里复制持久性。
	 */
	fc_seqpersistence = fc_cxt->rel ? fc_cxt->rel->rd_rel->relpersistence : fc_cxt->relation->relpersistence;
	if (fc_loggedEl)
	{
		if (fc_seqpersistence == RELPERSISTENCE_TEMP)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("cannot set logged status of a temporary sequence"),
					 parser_errposition(fc_cxt->pstate, fc_loggedEl->location)));
		else if (strcmp(fc_loggedEl->defname, "logged") == 0)
			fc_seqpersistence = RELPERSISTENCE_PERMANENT;
		else
			fc_seqpersistence = RELPERSISTENCE_UNLOGGED;
	}

	/*
	 * 构建一个 CREATE SEQUENCE 命令来创建序列对象，并将其添加到在此 CREATE/ALTER TABLE 之前要完成的事项列表中。
	 */
	fc_seqstmt = makeNode(CreateSeqStmt);
	fc_seqstmt->for_identity = fc_for_identity;
	fc_seqstmt->sequence = makeRangeVar(fc_snamespace, fc_sname, -1);
	fc_seqstmt->sequence->relpersistence = fc_seqpersistence;
	fc_seqstmt->options = fc_seqoptions;

	/*
	 * 如果指定了序列数据类型，将其添加到选项中。将其插入列表的开头而不是结尾；以防用户提供了自己的 AS 子句，“冗余选项”错误将指向它们的出现，而不是我们的合成选项。
	 */
	if (fc_seqtypid)
		fc_seqstmt->options = lcons(makeDefElem("as",
											 (Node *) makeTypeNameFromOid(fc_seqtypid, -1),
											 -1),
								 fc_seqstmt->options);

	/*
	 * 如果这是 ALTER ADD COLUMN，确保序列将由表的所有者拥有。当前用户可能是其他人（也许是超级用户，或者只是拥有角色的成员），但 SEQUENCE OWNED BY 机制会发出警告，除非表和序列具有完全相同的拥有角色。
	 */
	if (fc_cxt->rel)
		fc_seqstmt->ownerId = fc_cxt->rel->rd_rel->relowner;
	else
		fc_seqstmt->ownerId = InvalidOid;

	fc_cxt->blist = lappend(fc_cxt->blist, fc_seqstmt);

	/*
	 * 存储我们决定的身份序列名称。ALTER TABLE ... ADD COLUMN ... IDENTITY 需要这个，以便它可以用序列中的值填充新列，而序列与表的关联在 ALTER TABLE 之后才会设置。
	 */
	fc_column->identitySequence = fc_seqstmt->sequence;

	/*
	 * 构建一个 ALTER SEQUENCE ... OWNED BY 命令，将序列标记为由此列拥有，并将其添加到与此 CREATE/ALTER TABLE 一起要完成的适当事项列表中。在 CREATE 或 ALTER ADD COLUMN 中，必须在声明之后完成，因为我们还不知道列的 attnum。但是如果我们确实有 attnum（在 AT_AddIdentity 中），我们可以立即进行标记，从而改善某些 ALTER TABLE 的行为。
	 */
	fc_altseqstmt = makeNode(AlterSeqStmt);
	fc_altseqstmt->sequence = makeRangeVar(fc_snamespace, fc_sname, -1);
	fc_attnamelist = list_make3(makeString(fc_snamespace),
							 makeString(fc_cxt->relation->relname),
							 makeString(fc_column->colname));
	fc_altseqstmt->options = list_make1(makeDefElem("owned_by",
												 (Node *) fc_attnamelist, -1));
	fc_altseqstmt->for_identity = fc_for_identity;

	if (fc_col_exists)
		fc_cxt->blist = lappend(fc_cxt->blist, fc_altseqstmt);
	else
		fc_cxt->alist = lappend(fc_cxt->alist, fc_altseqstmt);

	if (fc_snamespace_p)
		*fc_snamespace_p = fc_snamespace;
	if (fc_sname_p)
		*fc_sname_p = fc_sname;
}

/***********************************************
 * transformColumnDefinition -
 *		在CREATE TABLE中转换单个ColumnDef
 *		也用于ALTER TABLE ADD COLUMN
 ***********************************************/
static void fc_transformColumnDefinition(CreateStmtContext *fc_cxt, ColumnDef *fc_column)
{
	bool		fc_is_serial;
	bool		fc_saw_nullable;
	bool		fc_saw_default;
	bool		fc_saw_identity;
	bool		fc_saw_generated;
	ListCell   *fc_clist;

	fc_cxt->columns = lappend(fc_cxt->columns, fc_column);

	/* 检查SERIAL伪类型 */
	fc_is_serial = false;
	if (fc_column->typeName
		&& list_length(fc_column->typeName->names) == 1
		&& !fc_column->typeName->pct_type)
	{
		char	   *fc_typname = strVal(linitial(fc_column->typeName->names));

		if (strcmp(fc_typname, "smallserial") == 0 ||
			strcmp(fc_typname, "serial2") == 0)
		{
			fc_is_serial = true;
			fc_column->typeName->names = NIL;
			fc_column->typeName->typeOid = INT2OID;
		}
		else if (strcmp(fc_typname, "serial") == 0 ||
				 strcmp(fc_typname, "serial4") == 0)
		{
			fc_is_serial = true;
			fc_column->typeName->names = NIL;
			fc_column->typeName->typeOid = INT4OID;
		}
		else if (strcmp(fc_typname, "bigserial") == 0 ||
				 strcmp(fc_typname, "serial8") == 0)
		{
			fc_is_serial = true;
			fc_column->typeName->names = NIL;
			fc_column->typeName->typeOid = INT8OID;
		}

		/*
		 * 我们必须明确拒绝"serial[]"，因为一旦我们设置了
		 * typeid，LookupTypeName将不会注意到arrayBounds。不过
		 * 对于serial(typmod)我们不需要任何特别的编码。
		 */
		if (fc_is_serial && fc_column->typeName->arrayBounds != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("array of serial is not implemented"),
					 parser_errposition(fc_cxt->pstate,
										fc_column->typeName->location)));
	}

	/* 对列类型声明进行必要的工作 */
	if (fc_column->typeName)
		fc_transformColumnType(fc_cxt, fc_column);

	/* 对SERIAL伪类型的特殊处理 */
	if (fc_is_serial)
	{
		char	   *fc_snamespace;
		char	   *fc_sname;
		char	   *fc_qstring;
		A_Const    *fc_snamenode;
		TypeCast   *fc_castnode;
		FuncCall   *fc_funccallnode;
		Constraint *fc_constraint;

		fc_generateSerialExtraStmts(fc_cxt, fc_column,
								 fc_column->typeName->typeOid, NIL,
								 false, false,
								 &fc_snamespace, &fc_sname);

		/*
		 * 为SERIAL创建适当的约束。我们这样做是完整的，
		 * 而不是捷径，以便能够检测到用户编写的任何冲突
		 * 约束（例如不同的DEFAULT）。
		 *
		 * 创建一个表示函数调用的表达式树
		 * nextval('sequencename')。在序列创建之前我们无法将
		 * 原始树简化为熟成形式，但并没有必要这么做。
		 */
		fc_qstring = quote_qualified_identifier(fc_snamespace, fc_sname);
		fc_snamenode = makeNode(A_Const);
		fc_snamenode->val.node.type = T_String;
		fc_snamenode->val.sval.sval = fc_qstring;
		fc_snamenode->location = -1;
		fc_castnode = makeNode(TypeCast);
		fc_castnode->typeName = SystemTypeName("regclass");
		fc_castnode->arg = (Node *) fc_snamenode;
		fc_castnode->location = -1;
		fc_funccallnode = makeFuncCall(SystemFuncName("nextval"),
									list_make1(fc_castnode),
									COERCE_EXPLICIT_CALL,
									-1);
		fc_constraint = makeNode(Constraint);
		fc_constraint->contype = CONSTR_DEFAULT;
		fc_constraint->location = -1;
		fc_constraint->raw_expr = (Node *) fc_funccallnode;
		fc_constraint->cooked_expr = NULL;
		fc_column->constraints = lappend(fc_column->constraints, fc_constraint);

		fc_constraint = makeNode(Constraint);
		fc_constraint->contype = CONSTR_NOTNULL;
		fc_constraint->location = -1;
		fc_column->constraints = lappend(fc_column->constraints, fc_constraint);
	}

	/* 处理列约束（如果有的话）... */
	fc_transformConstraintAttrs(fc_cxt, fc_column->constraints);

	fc_saw_nullable = false;
	fc_saw_default = false;
	fc_saw_identity = false;
	fc_saw_generated = false;

	foreach(fc_clist, fc_column->constraints)
	{
		Constraint *fc_constraint = lfirst_node(Constraint, fc_clist);

		switch (fc_constraint->contype)
		{
			case CONSTR_NULL:
				if (fc_saw_nullable && fc_column->is_not_null)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
									fc_column->colname, fc_cxt->relation->relname),
							 parser_errposition(fc_cxt->pstate,
												fc_constraint->location)));
				fc_column->is_not_null = false;
				fc_saw_nullable = true;
				break;

			case CONSTR_NOTNULL:
				if (fc_saw_nullable && !fc_column->is_not_null)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
									fc_column->colname, fc_cxt->relation->relname),
							 parser_errposition(fc_cxt->pstate,
												fc_constraint->location)));
				fc_column->is_not_null = true;
				fc_saw_nullable = true;
				break;

			case CONSTR_DEFAULT:
				if (fc_saw_default)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("multiple default values specified for column \"%s\" of table \"%s\"",
									fc_column->colname, fc_cxt->relation->relname),
							 parser_errposition(fc_cxt->pstate,
												fc_constraint->location)));
				fc_column->raw_default = fc_constraint->raw_expr;
				Assert(fc_constraint->cooked_expr == NULL);
				fc_saw_default = true;
				break;

			case CONSTR_IDENTITY:
				{
					Type		fc_ctype;
					Oid			fc_typeOid;

					if (fc_cxt->ofType)
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("identity columns are not supported on typed tables")));
					if (fc_cxt->partbound)
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("identity columns are not supported on partitions")));

					fc_ctype = typenameType(fc_cxt->pstate, fc_column->typeName, NULL);
					fc_typeOid = ((Form_pg_type) GETSTRUCT(fc_ctype))->oid;
					ReleaseSysCache(fc_ctype);

					if (fc_saw_identity)
						ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
								 errmsg("multiple identity specifications for column \"%s\" of table \"%s\"",
										fc_column->colname, fc_cxt->relation->relname),
								 parser_errposition(fc_cxt->pstate,
													fc_constraint->location)));

					fc_generateSerialExtraStmts(fc_cxt, fc_column,
											 fc_typeOid, fc_constraint->options,
											 true, false,
											 NULL, NULL);

					fc_column->identity = fc_constraint->generated_when;
					fc_saw_identity = true;

					/* 身份列隐式NOT NULL */
					if (fc_saw_nullable && !fc_column->is_not_null)
						ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
								 errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
										fc_column->colname, fc_cxt->relation->relname),
								 parser_errposition(fc_cxt->pstate,
													fc_constraint->location)));
					fc_column->is_not_null = true;
					fc_saw_nullable = true;
					break;
				}

			case CONSTR_GENERATED:
				if (fc_cxt->ofType)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("generated columns are not supported on typed tables")));
				if (fc_cxt->partbound)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("generated columns are not supported on partitions")));

				if (fc_saw_generated)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("multiple generation clauses specified for column \"%s\" of table \"%s\"",
									fc_column->colname, fc_cxt->relation->relname),
							 parser_errposition(fc_cxt->pstate,
												fc_constraint->location)));
				fc_column->generated = ATTRIBUTE_GENERATED_STORED;
				fc_column->raw_default = fc_constraint->raw_expr;
				Assert(fc_constraint->cooked_expr == NULL);
				fc_saw_generated = true;
				break;

			case CONSTR_CHECK:
				fc_cxt->ckconstraints = lappend(fc_cxt->ckconstraints, fc_constraint);
				break;

			case CONSTR_PRIMARY:
				if (fc_cxt->isforeign)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("primary key constraints are not supported on foreign tables"),
							 parser_errposition(fc_cxt->pstate,
												fc_constraint->location)));
				/* FALL THRU */

			case CONSTR_UNIQUE:
				if (fc_cxt->isforeign)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("unique constraints are not supported on foreign tables"),
							 parser_errposition(fc_cxt->pstate,
												fc_constraint->location)));
				if (fc_constraint->keys == NIL)
					fc_constraint->keys = list_make1(makeString(fc_column->colname));
				fc_cxt->ixconstraints = lappend(fc_cxt->ixconstraints, fc_constraint);
				break;

			case CONSTR_EXCLUSION:
				/* 语法不允许EXCLUDE作为列约束 */
				elog(ERROR, "column exclusion constraints are not supported");
				break;

			case CONSTR_FOREIGN:
				if (fc_cxt->isforeign)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("foreign key constraints are not supported on foreign tables"),
							 parser_errposition(fc_cxt->pstate,
												fc_constraint->location)));

				/*
				 * 填充当前属性的名称，并将其放入待处理的
				 * FK约束列表中。
				 */
				fc_constraint->fk_attrs = list_make1(makeString(fc_column->colname));
				fc_cxt->fkconstraints = lappend(fc_cxt->fkconstraints, fc_constraint);
				break;

			case CONSTR_ATTR_DEFERRABLE:
			case CONSTR_ATTR_NOT_DEFERRABLE:
			case CONSTR_ATTR_DEFERRED:
			case CONSTR_ATTR_IMMEDIATE:
				/* transformConstraintAttrs已处理这些 */
				break;

			default:
				elog(ERROR, "unrecognized constraint type: %d",
					 fc_constraint->contype);
				break;
		}

		if (fc_saw_default && fc_saw_identity)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("both default and identity specified for column \"%s\" of table \"%s\"",
							fc_column->colname, fc_cxt->relation->relname),
					 parser_errposition(fc_cxt->pstate,
										fc_constraint->location)));

		if (fc_saw_default && fc_saw_generated)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("both default and generation expression specified for column \"%s\" of table \"%s\"",
							fc_column->colname, fc_cxt->relation->relname),
					 parser_errposition(fc_cxt->pstate,
										fc_constraint->location)));

		if (fc_saw_identity && fc_saw_generated)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("both identity and generation expression specified for column \"%s\" of table \"%s\"",
							fc_column->colname, fc_cxt->relation->relname),
					 parser_errposition(fc_cxt->pstate,
										fc_constraint->location)));
	}

	/*
	 * 如果需要，生成ALTER FOREIGN TABLE ALTER COLUMN语句，以在创建后
	 * 将每列外部数据包装器选项添加到此列。
	 */
	if (fc_column->fdwoptions != NIL)
	{
		AlterTableStmt *fc_stmt;
		AlterTableCmd *fc_cmd;

		fc_cmd = makeNode(AlterTableCmd);
		fc_cmd->subtype = AT_AlterColumnGenericOptions;
		fc_cmd->name = fc_column->colname;
		fc_cmd->def = (Node *) fc_column->fdwoptions;
		fc_cmd->behavior = DROP_RESTRICT;
		fc_cmd->missing_ok = false;

		fc_stmt = makeNode(AlterTableStmt);
		fc_stmt->relation = fc_cxt->relation;
		fc_stmt->cmds = NIL;
		fc_stmt->objtype = OBJECT_FOREIGN_TABLE;
		fc_stmt->cmds = lappend(fc_stmt->cmds, fc_cmd);

		fc_cxt->alist = lappend(fc_cxt->alist, fc_stmt);
	}
}

/*
 * transformTableConstraint
 *		在CREATE TABLE或ALTER TABLE中转换约束节点
 ***********************************************/
static void fc_transformTableConstraint(CreateStmtContext *fc_cxt, Constraint *fc_constraint)
{
	switch (fc_constraint->contype)
	{
		case CONSTR_PRIMARY:
			if (fc_cxt->isforeign)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("primary key constraints are not supported on foreign tables"),
						 parser_errposition(fc_cxt->pstate,
											fc_constraint->location)));
			fc_cxt->ixconstraints = lappend(fc_cxt->ixconstraints, fc_constraint);
			break;

		case CONSTR_UNIQUE:
			if (fc_cxt->isforeign)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("unique constraints are not supported on foreign tables"),
						 parser_errposition(fc_cxt->pstate,
											fc_constraint->location)));
			fc_cxt->ixconstraints = lappend(fc_cxt->ixconstraints, fc_constraint);
			break;

		case CONSTR_EXCLUSION:
			if (fc_cxt->isforeign)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("exclusion constraints are not supported on foreign tables"),
						 parser_errposition(fc_cxt->pstate,
											fc_constraint->location)));
			if (fc_cxt->ispartitioned)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("exclusion constraints are not supported on partitioned tables"),
						 parser_errposition(fc_cxt->pstate,
											fc_constraint->location)));
			fc_cxt->ixconstraints = lappend(fc_cxt->ixconstraints, fc_constraint);
			break;

		case CONSTR_CHECK:
			fc_cxt->ckconstraints = lappend(fc_cxt->ckconstraints, fc_constraint);
			break;

		case CONSTR_FOREIGN:
			if (fc_cxt->isforeign)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("foreign key constraints are not supported on foreign tables"),
						 parser_errposition(fc_cxt->pstate,
											fc_constraint->location)));
			fc_cxt->fkconstraints = lappend(fc_cxt->fkconstraints, fc_constraint);
			break;

		case CONSTR_NULL:
		case CONSTR_NOTNULL:
		case CONSTR_DEFAULT:
		case CONSTR_ATTR_DEFERRABLE:
		case CONSTR_ATTR_NOT_DEFERRABLE:
		case CONSTR_ATTR_DEFERRED:
		case CONSTR_ATTR_IMMEDIATE:
			elog(ERROR, "invalid context for constraint type %d",
				 fc_constraint->contype);
			break;

		default:
			elog(ERROR, "unrecognized constraint type: %d",
				 fc_constraint->contype);
			break;
	}
}

/*
 * transformTableLikeClause
 *
 * 将CREATE TABLE语句的LIKE <srctable>部分转换为
 * 列定义，以重新创建<srctable>的用户定义列部分。
 * 此外，如果有任何在此时无法完全处理的LIKE选项，
 * 将TableLikeClause添加到cxt->likeclauses中，这将导致
 * utility.c在新表创建后调用expandTableLikeClause()。
 ***********************************************/
static void fc_transformTableLikeClause(CreateStmtContext *fc_cxt, TableLikeClause *fc_table_like_clause)
{
	AttrNumber	fc_parent_attno;
	Relation	fc_relation;
	TupleDesc	fc_tupleDesc;
	AclResult	fc_aclresult;
	char	   *fc_comment;
	ParseCallbackState fc_pcbstate;

	setup_parser_errposition_callback(&fc_pcbstate, fc_cxt->pstate,
									  fc_table_like_clause->relation->location);

	/* 在许多情况下，我们可以支持LIKE，但另日再说 */
	if (fc_cxt->isforeign)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("LIKE is not supported for creating foreign tables")));

	/* 打开LIKE子句引用的关系 */
	fc_relation = relation_openrv(fc_table_like_clause->relation, AccessShareLock);

	if (fc_relation->rd_rel->relkind != RELKIND_RELATION &&
		fc_relation->rd_rel->relkind != RELKIND_VIEW &&
		fc_relation->rd_rel->relkind != RELKIND_MATVIEW &&
		fc_relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
		fc_relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
		fc_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" is invalid in LIKE clause",
						RelationGetRelationName(fc_relation)),
				 errdetail_relkind_not_supported(fc_relation->rd_rel->relkind)));

	cancel_parser_errposition_callback(&fc_pcbstate);

	/*
	 * 检查权限
	 */
	if (fc_relation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
	{
		fc_aclresult = pg_type_aclcheck(fc_relation->rd_rel->reltype, GetUserId(),
									 ACL_USAGE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_TYPE,
						   RelationGetRelationName(fc_relation));
	}
	else
	{
		fc_aclresult = pg_class_aclcheck(RelationGetRelid(fc_relation), GetUserId(),
									  ACL_SELECT);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, get_relkind_objtype(fc_relation->rd_rel->relkind),
						   RelationGetRelationName(fc_relation));
	}

	fc_tupleDesc = RelationGetDescr(fc_relation);

	/*
	 * 将复制的属性插入到新表定义的cxt中。
	 * 我们必须现在这样做，以便它们出现在
	 * LIKE子句所需的相对位置中，如SQL99要求。
	 */
	for (fc_parent_attno = 1; fc_parent_attno <= fc_tupleDesc->natts;
		 fc_parent_attno++)
	{
		Form_pg_attribute fc_attribute = TupleDescAttr(fc_tupleDesc,
													fc_parent_attno - 1);
		char	   *fc_attributeName = NameStr(fc_attribute->attname);
		ColumnDef  *fc_def;

		/*
		 * 忽略父级中的已删除列。
		 */
		if (fc_attribute->attisdropped)
			continue;

		/*
		 * 创建一个新列，该列标记为NOT inherited。
		 *
		 * 对于约束，仅NOT NULL约束由新的
		 * 列定义继承，符合SQL99。
		 */
		fc_def = makeNode(ColumnDef);
		fc_def->colname = pstrdup(fc_attributeName);
		fc_def->typeName = makeTypeNameFromOid(fc_attribute->atttypid,
											fc_attribute->atttypmod);
		fc_def->inhcount = 0;
		fc_def->is_local = true;
		fc_def->is_not_null = fc_attribute->attnotnull;
		fc_def->is_from_type = false;
		fc_def->storage = 0;
		fc_def->raw_default = NULL;
		fc_def->cooked_default = NULL;
		fc_def->collClause = NULL;
		fc_def->collOid = fc_attribute->attcollation;
		fc_def->constraints = NIL;
		fc_def->location = -1;

		/*
		 * 添加到列列表
		 */
		fc_cxt->columns = lappend(fc_cxt->columns, fc_def);

		/*
		 * 尽管我们现在不传递列的默认值/生成
		 * 表达式，但如果合适，我们需要标记为GENERATED。
		 */
		if (fc_attribute->atthasdef && fc_attribute->attgenerated &&
			(fc_table_like_clause->options & CREATE_TABLE_LIKE_GENERATED))
			fc_def->generated = fc_attribute->attgenerated;

		/*
		 * 如果需要，复制身份
		 */
		if (fc_attribute->attidentity &&
			(fc_table_like_clause->options & CREATE_TABLE_LIKE_IDENTITY))
		{
			Oid			fc_seq_relid;
			List	   *fc_seq_options;

			/*
			 * 查找由旧列拥有的序列；提取序列参数；
			 * 构建新的创建序列命令
			 */
			fc_seq_relid = getIdentitySequence(RelationGetRelid(fc_relation), fc_attribute->attnum, false);
			fc_seq_options = sequence_options(fc_seq_relid);
			fc_generateSerialExtraStmts(fc_cxt, fc_def,
									 InvalidOid, fc_seq_options,
									 true, false,
									 NULL, NULL);
			fc_def->identity = fc_attribute->attidentity;
		}

		/* 同样，如果请求则复制存储 */
		if (fc_table_like_clause->options & CREATE_TABLE_LIKE_STORAGE)
			fc_def->storage = fc_attribute->attstorage;
		else
			fc_def->storage = 0;

		/* 同样，如果请求则复制压缩 */
		if ((fc_table_like_clause->options & CREATE_TABLE_LIKE_COMPRESSION) != 0
			&& CompressionMethodIsValid(fc_attribute->attcompression))
			fc_def->compression =
				pstrdup(GetCompressionMethodName(fc_attribute->attcompression));
		else
			fc_def->compression = NULL;

		/* 同样，如果请求则复制注释 */
		if ((fc_table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) &&
			(fc_comment = GetComment(fc_attribute->attrelid,
								  RelationRelationId,
								  fc_attribute->attnum)) != NULL)
		{
			CommentStmt *fc_stmt = makeNode(CommentStmt);

			fc_stmt->objtype = OBJECT_COLUMN;
			fc_stmt->object = (Node *) list_make3(makeString(fc_cxt->relation->schemaname),
											   makeString(fc_cxt->relation->relname),
											   makeString(fc_def->colname));
			fc_stmt->comment = fc_comment;

			fc_cxt->alist = lappend(fc_cxt->alist, fc_stmt);
		}
	}

	/*
	 * 我们尚无法处理默认值、CHECK约束、索引或
	 * 统计信息，因为我们尚不清楚复制的
	 * 列在最终表中的列号。如果指定了任何
	 * 选项，将LIKE子句添加到cxt->likeclauses中以便
	 * expandTableLikeClause在我们知道后调用。
	 * 此外，记住关系OID以确保expandTableLikeClause
	 * 确实打开相同的表。
	 */
	if (fc_table_like_clause->options &
		(CREATE_TABLE_LIKE_DEFAULTS |
		 CREATE_TABLE_LIKE_GENERATED |
		 CREATE_TABLE_LIKE_CONSTRAINTS |
		 CREATE_TABLE_LIKE_INDEXES |
		 CREATE_TABLE_LIKE_STATISTICS))
	{
		fc_table_like_clause->relationOid = RelationGetRelid(fc_relation);
		fc_cxt->likeclauses = lappend(fc_cxt->likeclauses, fc_table_like_clause);
	}

	/*
	 * 关闭父级关系，但在xact提交之前保持
	 * 对其的AccessShareLock。这将防止其他人
	 * 删除或ALTER父级，直到我们可以运行
	 * expandTableLikeClause。
	 */
	table_close(fc_relation, NoLock);
}

/*
 * expandTableLikeClause
 *
 * 处理需要知道新表的列最终列号的 LIKE 选项。
 * 这在我们为新表执行 DefineRelation 之后执行。它返回一个实用程序
 * 命令列表，这些命令应该被运行以生成索引等。
 */
List * expandTableLikeClause(RangeVar *fc_heapRel, TableLikeClause *fc_table_like_clause)
{
	List	   *fc_result = NIL;
	List	   *fc_atsubcmds = NIL;
	AttrNumber	fc_parent_attno;
	Relation	fc_relation;
	Relation	fc_childrel;
	TupleDesc	fc_tupleDesc;
	TupleConstr *fc_constr;
	AttrMap    *fc_attmap;
	char	   *fc_comment;

	/*
	 * 打开LIKE子句引用的关系。我们应该仍然拥有通过transformTableLikeClause获得的表锁（如果没有，这将抛出断言失败）。因此，不需要重新检查权限等。我们必须通过OID而不是名称打开关系，以确保获取相同的表。
	 */
	if (!OidIsValid(fc_table_like_clause->relationOid))
		elog(ERROR, "expandTableLikeClause called on untransformed LIKE clause");

	fc_relation = relation_open(fc_table_like_clause->relationOid, NoLock);

	fc_tupleDesc = RelationGetDescr(fc_relation);
	fc_constr = fc_tupleDesc->constr;

	/*
	 * 打开新创建的子关系；我们也对其持有锁。
	 */
	fc_childrel = relation_openrv(fc_heapRel, NoLock);

	/*
	 * 构建一个LIKE关系的attnos到子关系的映射。这会重新检查类型匹配等，尽管由于两个表都被锁定，不应该出现失败。
	 */
	fc_attmap = build_attrmap_by_name(RelationGetDescr(fc_childrel),
								   fc_tupleDesc);

	/*
	 * 处理默认值（如果需要）。
	 */
	if ((fc_table_like_clause->options &
		 (CREATE_TABLE_LIKE_DEFAULTS | CREATE_TABLE_LIKE_GENERATED)) &&
		fc_constr != NULL)
	{
		for (fc_parent_attno = 1; fc_parent_attno <= fc_tupleDesc->natts;
			 fc_parent_attno++)
		{
			Form_pg_attribute fc_attribute = TupleDescAttr(fc_tupleDesc,
														fc_parent_attno - 1);

			/*
			 * 忽略父级中删除的列。
			 */
			if (fc_attribute->attisdropped)
				continue;

			/*
			 * 复制默认值，如果存在并且应该被复制。我们对普通默认表达式和生成的默认值有单独的选项。
			 */
			if (fc_attribute->atthasdef &&
				(fc_attribute->attgenerated ?
				 (fc_table_like_clause->options & CREATE_TABLE_LIKE_GENERATED) :
				 (fc_table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)))
			{
				Node	   *fc_this_default = NULL;
				AttrDefault *fc_attrdef = fc_constr->defval;
				AlterTableCmd *fc_atsubcmd;
				bool		fc_found_whole_row;

				/* 在约束结构中寻找默认值 */
				for (int fc_i = 0; fc_i < fc_constr->num_defval; fc_i++)
				{
					if (fc_attrdef[fc_i].adnum == fc_parent_attno)
					{
						fc_this_default = stringToNode(fc_attrdef[fc_i].adbin);
						break;
					}
				}
				if (fc_this_default == NULL)
					elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
						 fc_parent_attno, RelationGetRelationName(fc_relation));

				fc_atsubcmd = makeNode(AlterTableCmd);
				fc_atsubcmd->subtype = AT_CookedColumnDefault;
				fc_atsubcmd->num = fc_attmap->attnums[fc_parent_attno - 1];
				fc_atsubcmd->def = map_variable_attnos(fc_this_default,
													1, 0,
													fc_attmap,
													InvalidOid,
													&fc_found_whole_row);

				/*
				 * 出于与下面约束相同的原因防止这种情况发生。请注意，默认值不能包含任何变量，因此错误消息提到生成的列是可以接受的。
				 */
				if (fc_found_whole_row)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cannot convert whole-row table reference"),
							 errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
									   NameStr(fc_attribute->attname),
									   RelationGetRelationName(fc_relation))));

				fc_atsubcmds = lappend(fc_atsubcmds, fc_atsubcmd);
			}
		}
	}

	/*
	 * 如果请求，复制CHECK约束，小心调整属性编号以使其匹配子关系。
	 */
	if ((fc_table_like_clause->options & CREATE_TABLE_LIKE_CONSTRAINTS) &&
		fc_constr != NULL)
	{
		int			fc_ccnum;

		for (fc_ccnum = 0; fc_ccnum < fc_constr->num_check; fc_ccnum++)
		{
			char	   *fc_ccname = fc_constr->check[fc_ccnum].ccname;
			char	   *fc_ccbin = fc_constr->check[fc_ccnum].ccbin;
			bool		fc_ccnoinherit = fc_constr->check[fc_ccnum].ccnoinherit;
			Node	   *fc_ccbin_node;
			bool		fc_found_whole_row;
			Constraint *fc_n;
			AlterTableCmd *fc_atsubcmd;

			fc_ccbin_node = map_variable_attnos(stringToNode(fc_ccbin),
											 1, 0,
											 fc_attmap,
											 InvalidOid, &fc_found_whole_row);

			/*
			 * 我们拒绝整行变量，因为LIKE的整个要点是，新表的行类型可能会与父表的行类型分歧。因此，虽然当前的转换可能是可能的，但不能保证在未来能够正常工作。
			 */
			if (fc_found_whole_row)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot convert whole-row table reference"),
						 errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
								   fc_ccname,
								   RelationGetRelationName(fc_relation))));

			fc_n = makeNode(Constraint);
			fc_n->contype = CONSTR_CHECK;
			fc_n->conname = pstrdup(fc_ccname);
			fc_n->location = -1;
			fc_n->is_no_inherit = fc_ccnoinherit;
			fc_n->raw_expr = NULL;
			fc_n->cooked_expr = nodeToString(fc_ccbin_node);

			/* 我们可以跳过验证，因为新表应该是空的。 */
			fc_n->skip_validation = true;
			fc_n->initially_valid = true;

			fc_atsubcmd = makeNode(AlterTableCmd);
			fc_atsubcmd->subtype = AT_AddConstraint;
			fc_atsubcmd->def = (Node *) fc_n;
			fc_atsubcmds = lappend(fc_atsubcmds, fc_atsubcmd);

			/* 复制约束的注释 */
			if ((fc_table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) &&
				(fc_comment = GetComment(get_relation_constraint_oid(RelationGetRelid(fc_relation),
																  fc_n->conname, false),
									  ConstraintRelationId,
									  0)) != NULL)
			{
				CommentStmt *fc_stmt = makeNode(CommentStmt);

				fc_stmt->objtype = OBJECT_TABCONSTRAINT;
				fc_stmt->object = (Node *) list_make3(makeString(fc_heapRel->schemaname),
												   makeString(fc_heapRel->relname),
												   makeString(fc_n->conname));
				fc_stmt->comment = fc_comment;

				fc_result = lappend(fc_result, fc_stmt);
			}
		}
	}

	/*
	 * 如果我们在上面生成了任何ALTER TABLE操作，将它们包裹在一个单独的ALTER TABLE命令中。将其放在结果的前面，以便在我们上面做的任何CommentStmts之前运行。
	 */
	if (fc_atsubcmds)
	{
		AlterTableStmt *fc_atcmd = makeNode(AlterTableStmt);

		fc_atcmd->relation = copyObject(fc_heapRel);
		fc_atcmd->cmds = fc_atsubcmds;
		fc_atcmd->objtype = OBJECT_TABLE;
		fc_atcmd->missing_ok = false;
		fc_result = lcons(fc_atcmd, fc_result);
	}

	/*
	 * 如有需要，处理索引。
	 */
	if ((fc_table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) &&
		fc_relation->rd_rel->relhasindex)
	{
		List	   *fc_parent_indexes;
		ListCell   *fc_l;

		fc_parent_indexes = RelationGetIndexList(fc_relation);

		foreach(fc_l, fc_parent_indexes)
		{
			Oid			fc_parent_index_oid = lfirst_oid(fc_l);
			Relation	fc_parent_index;
			IndexStmt  *fc_index_stmt;

			fc_parent_index = index_open(fc_parent_index_oid, AccessShareLock);

			/* 构建CREATE INDEX语句以重新创建parent_index */
			fc_index_stmt = generateClonedIndexStmt(fc_heapRel,
												 fc_parent_index,
												 fc_attmap,
												 NULL);

			/* 如果请求，复制索引的注释 */
			if (fc_table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
			{
				fc_comment = GetComment(fc_parent_index_oid, RelationRelationId, 0);

				/*
				 * 我们使用IndexStmt的idxcomment选项，以便不必现在知道索引将具有什么名称。
				 */
				fc_index_stmt->idxcomment = fc_comment;
			}

			fc_result = lappend(fc_result, fc_index_stmt);

			index_close(fc_parent_index, AccessShareLock);
		}
	}

	/*
	 * 如有需要，处理扩展统计信息。
	 */
	if (fc_table_like_clause->options & CREATE_TABLE_LIKE_STATISTICS)
	{
		List	   *fc_parent_extstats;
		ListCell   *fc_l;

		fc_parent_extstats = RelationGetStatExtList(fc_relation);

		foreach(fc_l, fc_parent_extstats)
		{
			Oid			fc_parent_stat_oid = lfirst_oid(fc_l);
			CreateStatsStmt *fc_stats_stmt;

			fc_stats_stmt = fc_generateClonedExtStatsStmt(fc_heapRel,
													RelationGetRelid(fc_childrel),
													fc_parent_stat_oid,
													fc_attmap);

			/* 如果请求，复制统计对象的注释 */
			if (fc_table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
			{
				fc_comment = GetComment(fc_parent_stat_oid, StatisticExtRelationId, 0);

				/*
				 * 我们使用CreateStatsStmt的stxcomment选项，以便不必现在知道统计将具有什么名称。
				 */
				fc_stats_stmt->stxcomment = fc_comment;
			}

			fc_result = lappend(fc_result, fc_stats_stmt);
		}

		list_free(fc_parent_extstats);
	}

	/* 完成子关系 */
	table_close(fc_childrel, NoLock);

	/*
	 * 关闭父关系，但在事务提交前保持对其的AccessShareLock。这将防止其他人删除或更改父关系，直到子关系被提交。
	 */
	table_close(fc_relation, NoLock);

	return fc_result;
}

static void fc_transformOfType(CreateStmtContext *fc_cxt, TypeName *fc_ofTypename)
{
	HeapTuple	fc_tuple;
	TupleDesc	fc_tupdesc;
	int			fc_i;
	Oid			fc_ofTypeId;

	AssertArg(fc_ofTypename);

	fc_tuple = typenameType(NULL, fc_ofTypename, NULL);
	check_of_type(fc_tuple);
	fc_ofTypeId = ((Form_pg_type) GETSTRUCT(fc_tuple))->oid;
	fc_ofTypename->typeOid = fc_ofTypeId; /* 缓存以便稍后使用 */

	fc_tupdesc = lookup_rowtype_tupdesc(fc_ofTypeId, -1);
	for (fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_i);
		ColumnDef  *fc_n;

		if (fc_attr->attisdropped)
			continue;

		fc_n = makeNode(ColumnDef);
		fc_n->colname = pstrdup(NameStr(fc_attr->attname));
		fc_n->typeName = makeTypeNameFromOid(fc_attr->atttypid, fc_attr->atttypmod);
		fc_n->inhcount = 0;
		fc_n->is_local = true;
		fc_n->is_not_null = false;
		fc_n->is_from_type = true;
		fc_n->storage = 0;
		fc_n->raw_default = NULL;
		fc_n->cooked_default = NULL;
		fc_n->collClause = NULL;
		fc_n->collOid = fc_attr->attcollation;
		fc_n->constraints = NIL;
		fc_n->location = -1;
		fc_cxt->columns = lappend(fc_cxt->columns, fc_n);
	}
	ReleaseTupleDesc(fc_tupdesc);

	ReleaseSysCache(fc_tuple);
}

/*
 * 使用已经存在的索引"source_idx"的信息生成一个IndexStmt节点。
 *
 * heapRel被存储到IndexStmt的关系字段中，但我们不再使用它；一些调用者传入NULL，如果他们不需要它是有效的。
 * （目标关系可能还不存在，因此我们不能尝试访问它。）
 *
 * 表达式变量中的属性编号根据attmap进行调整。
 *
 * 如果constraintOid不为NULL，我们在其中存储与该索引相关联的任何约束的OID。
 *
 * 与transformIndexConstraint不同，我们不会强制主键列为NOT NULL。 这个更大的克隆过程的一部分应该单独克隆它们的NOT NULL状态（如果那失败，DefineIndex将会抱怨）。
 */
IndexStmt *
generateClonedIndexStmt(RangeVar *fc_heapRel, Relation fc_source_idx,
						const AttrMap *fc_attmap,
						Oid *fc_constraintOid)
{
	Oid			fc_source_relid = RelationGetRelid(fc_source_idx);
	HeapTuple	fc_ht_idxrel;
	HeapTuple	fc_ht_idx;
	HeapTuple	fc_ht_am;
	Form_pg_class fc_idxrelrec;
	Form_pg_index fc_idxrec;
	Form_pg_am	fc_amrec;
	oidvector  *fc_indcollation;
	oidvector  *fc_indclass;
	IndexStmt  *fc_index;
	List	   *fc_indexprs;
	ListCell   *fc_indexpr_item;
	Oid			fc_indrelid;
	int			fc_keyno;
	Oid			fc_keycoltype;
	Datum		fc_datum;
	bool		fc_isnull;

	if (fc_constraintOid)
		*fc_constraintOid = InvalidOid;

	/*
	 * 获取源索引的pg_class元组。我们不能使用relu缓存条目中的副本，因为它不包括可选字段。
	 */
	fc_ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_source_relid));
	if (!HeapTupleIsValid(fc_ht_idxrel))
		elog(ERROR, "cache lookup failed for relation %u", fc_source_relid);
	fc_idxrelrec = (Form_pg_class) GETSTRUCT(fc_ht_idxrel);

	/* 从relcache条目获取源索引的pg_index元组 */
	fc_ht_idx = fc_source_idx->rd_indextuple;
	fc_idxrec = (Form_pg_index) GETSTRUCT(fc_ht_idx);
	fc_indrelid = fc_idxrec->indrelid;

	/* 获取索引的访问方法的pg_am元组 */
	fc_ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(fc_idxrelrec->relam));
	if (!HeapTupleIsValid(fc_ht_am))
		elog(ERROR, "cache lookup failed for access method %u",
			 fc_idxrelrec->relam);
	fc_amrec = (Form_pg_am) GETSTRUCT(fc_ht_am);

	/* 从pg_index元组提取indcollation */
	fc_datum = SysCacheGetAttr(INDEXRELID, fc_ht_idx,
							Anum_pg_index_indcollation, &fc_isnull);
	Assert(!fc_isnull);
	fc_indcollation = (oidvector *) DatumGetPointer(fc_datum);

	/* 从pg_index元组提取indclass */
	fc_datum = SysCacheGetAttr(INDEXRELID, fc_ht_idx,
							Anum_pg_index_indclass, &fc_isnull);
	Assert(!fc_isnull);
	fc_indclass = (oidvector *) DatumGetPointer(fc_datum);

	/* 开始构建IndexStmt */
	fc_index = makeNode(IndexStmt);
	fc_index->relation = fc_heapRel;
	fc_index->accessMethod = pstrdup(NameStr(fc_amrec->amname));
	if (OidIsValid(fc_idxrelrec->reltablespace))
		fc_index->tableSpace = get_tablespace_name(fc_idxrelrec->reltablespace);
	else
		fc_index->tableSpace = NULL;
	fc_index->excludeOpNames = NIL;
	fc_index->idxcomment = NULL;
	fc_index->indexOid = InvalidOid;
	fc_index->oldNode = InvalidOid;
	fc_index->oldCreateSubid = InvalidSubTransactionId;
	fc_index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
	fc_index->unique = fc_idxrec->indisunique;
	fc_index->nulls_not_distinct = fc_idxrec->indnullsnotdistinct;
	fc_index->primary = fc_idxrec->indisprimary;
	fc_index->transformed = true;	/* 不需要transformIndexStmt */
	fc_index->concurrent = false;
	fc_index->if_not_exists = false;
	fc_index->reset_default_tblspc = false;

	/*
	 * 我们不试图保留源索引的名称；相反，仅让DefineIndex()选择一个合理的名称。 （如果我们试图保留名称，除非源表在不同的模式中，否则我们会遇到重复关系名称的失败。）
	 */
	fc_index->idxname = NULL;

	/*
	 * 如果索引被标记为 PRIMARY 或具有排除条件，那么它
	 * 肯定来自约束；否则，如果未标记为 UNIQUE，则
	 * 肯定不是。如果它来自或可能来自约束，我们必须
	 * 获取 pg_constraint 记录。
	 */
	if (fc_index->primary || fc_index->unique || fc_idxrec->indisexclusion)
	{
		Oid			fc_constraintId = get_index_constraint(fc_source_relid);

		if (OidIsValid(fc_constraintId))
		{
			HeapTuple	fc_ht_constr;
			Form_pg_constraint fc_conrec;

			if (fc_constraintOid)
				*fc_constraintOid = fc_constraintId;

			fc_ht_constr = SearchSysCache1(CONSTROID,
										ObjectIdGetDatum(fc_constraintId));
			if (!HeapTupleIsValid(fc_ht_constr))
				elog(ERROR, "cache lookup failed for constraint %u",
					 fc_constraintId);
			fc_conrec = (Form_pg_constraint) GETSTRUCT(fc_ht_constr);

			fc_index->isconstraint = true;
			fc_index->deferrable = fc_conrec->condeferrable;
			fc_index->initdeferred = fc_conrec->condeferred;

			/* 如果是排除约束，我们需要操作符名称 */
			if (fc_idxrec->indisexclusion)
			{
				Datum	   *fc_elems;
				int			fc_nElems;
				int			fc_i;

				Assert(fc_conrec->contype == CONSTRAINT_EXCLUSION);
				/* 从 pg_constraint 元组中提取操作符 OID */
				fc_datum = SysCacheGetAttr(CONSTROID, fc_ht_constr,
										Anum_pg_constraint_conexclop,
										&fc_isnull);
				if (fc_isnull)
					elog(ERROR, "null conexclop for constraint %u",
						 fc_constraintId);

				deconstruct_array(DatumGetArrayTypeP(fc_datum),
								  OIDOID, sizeof(Oid), true, TYPALIGN_INT,
								  &fc_elems, NULL, &fc_nElems);

				for (fc_i = 0; fc_i < fc_nElems; fc_i++)
				{
					Oid			fc_operid = DatumGetObjectId(fc_elems[fc_i]);
					HeapTuple	fc_opertup;
					Form_pg_operator fc_operform;
					char	   *fc_oprname;
					char	   *fc_nspname;
					List	   *fc_namelist;

					fc_opertup = SearchSysCache1(OPEROID,
											  ObjectIdGetDatum(fc_operid));
					if (!HeapTupleIsValid(fc_opertup))
						elog(ERROR, "cache lookup failed for operator %u",
							 fc_operid);
					fc_operform = (Form_pg_operator) GETSTRUCT(fc_opertup);
					fc_oprname = pstrdup(NameStr(fc_operform->oprname));
					/* 为了简单起见，我们总是对操作符名称进行模式限定 */
					fc_nspname = get_namespace_name(fc_operform->oprnamespace);
					fc_namelist = list_make2(makeString(fc_nspname),
										  makeString(fc_oprname));
					fc_index->excludeOpNames = lappend(fc_index->excludeOpNames,
													fc_namelist);
					ReleaseSysCache(fc_opertup);
				}
			}

			ReleaseSysCache(fc_ht_constr);
		}
		else
			fc_index->isconstraint = false;
	}
	else
		fc_index->isconstraint = false;

	/* 获取索引表达式（如果有的话） */
	fc_datum = SysCacheGetAttr(INDEXRELID, fc_ht_idx,
							Anum_pg_index_indexprs, &fc_isnull);
	if (!fc_isnull)
	{
		char	   *fc_exprsString;

		fc_exprsString = TextDatumGetCString(fc_datum);
		fc_indexprs = (List *) stringToNode(fc_exprsString);
	}
	else
		fc_indexprs = NIL;

	/* 构建 IndexElem 列表 */
	fc_index->indexParams = NIL;
	fc_index->indexIncludingParams = NIL;

	fc_indexpr_item = list_head(fc_indexprs);
	for (fc_keyno = 0; fc_keyno < fc_idxrec->indnkeyatts; fc_keyno++)
	{
		IndexElem  *fc_iparam;
		AttrNumber	fc_attnum = fc_idxrec->indkey.values[fc_keyno];
		Form_pg_attribute fc_attr = TupleDescAttr(RelationGetDescr(fc_source_idx),
											   fc_keyno);
		int16		fc_opt = fc_source_idx->rd_indoption[fc_keyno];

		fc_iparam = makeNode(IndexElem);

		if (AttributeNumberIsValid(fc_attnum))
		{
			/* 简单索引列 */
			char	   *fc_attname;

			fc_attname = get_attname(fc_indrelid, fc_attnum, false);
			fc_keycoltype = get_atttype(fc_indrelid, fc_attnum);

			fc_iparam->name = fc_attname;
			fc_iparam->expr = NULL;
		}
		else
		{
			/* 表达式索引 */
			Node	   *fc_indexkey;
			bool		fc_found_whole_row;

			if (fc_indexpr_item == NULL)
				elog(ERROR, "too few entries in indexprs list");
			fc_indexkey = (Node *) lfirst(fc_indexpr_item);
			fc_indexpr_item = lnext(fc_indexprs, fc_indexpr_item);

			/* 调整 Vars 以匹配新表的列编号 */
			fc_indexkey = map_variable_attnos(fc_indexkey,
										   1, 0,
										   fc_attmap,
										   InvalidOid, &fc_found_whole_row);

			/* 如同 expandTableLikeClause，拒绝整个行变量 */
			if (fc_found_whole_row)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot convert whole-row table reference"),
						 errdetail("Index \"%s\" contains a whole-row table reference.",
								   RelationGetRelationName(fc_source_idx))));

			fc_iparam->name = NULL;
			fc_iparam->expr = fc_indexkey;

			fc_keycoltype = exprType(fc_indexkey);
		}

		/* 复制原始索引列名称 */
		fc_iparam->indexcolname = pstrdup(NameStr(fc_attr->attname));

		/* 如果不是默认值，则添加排序规则名称 */
		fc_iparam->collation = get_collation(fc_indcollation->values[fc_keyno], fc_keycoltype);

		/* 如果不是默认值，则添加操作符类名称 */
		fc_iparam->opclass = get_opclass(fc_indclass->values[fc_keyno], fc_keycoltype);
		fc_iparam->opclassopts =
			untransformRelOptions(get_attoptions(fc_source_relid, fc_keyno + 1));

		fc_iparam->ordering = SORTBY_DEFAULT;
		fc_iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;

		/* 如果必要，调整选项 */
		if (fc_source_idx->rd_indam->amcanorder)
		{
			/*
			 * 如果支持排序顺序，复制 DESC 和 NULLS 选项。但
			 * 不要不必要地设置非默认设置，以提高
			 * 识别与约束索引等价的机会。
			 */
			if (fc_opt & INDOPTION_DESC)
			{
				fc_iparam->ordering = SORTBY_DESC;
				if ((fc_opt & INDOPTION_NULLS_FIRST) == 0)
					fc_iparam->nulls_ordering = SORTBY_NULLS_LAST;
			}
			else
			{
				if (fc_opt & INDOPTION_NULLS_FIRST)
					fc_iparam->nulls_ordering = SORTBY_NULLS_FIRST;
			}
		}

		fc_index->indexParams = lappend(fc_index->indexParams, fc_iparam);
	}

	/* 单独处理包含的列 */
	for (fc_keyno = fc_idxrec->indnkeyatts; fc_keyno < fc_idxrec->indnatts; fc_keyno++)
	{
		IndexElem  *fc_iparam;
		AttrNumber	fc_attnum = fc_idxrec->indkey.values[fc_keyno];
		Form_pg_attribute fc_attr = TupleDescAttr(RelationGetDescr(fc_source_idx),
											   fc_keyno);

		fc_iparam = makeNode(IndexElem);

		if (AttributeNumberIsValid(fc_attnum))
		{
			/* 简单索引列 */
			char	   *fc_attname;

			fc_attname = get_attname(fc_indrelid, fc_attnum, false);

			fc_iparam->name = fc_attname;
			fc_iparam->expr = NULL;
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("expressions are not supported in included columns")));

		/* 复制原始索引列名称 */
		fc_iparam->indexcolname = pstrdup(NameStr(fc_attr->attname));

		fc_index->indexIncludingParams = lappend(fc_index->indexIncludingParams, fc_iparam);
	}
	/* 如果有，复制 reloptions */
	fc_datum = SysCacheGetAttr(RELOID, fc_ht_idxrel,
							Anum_pg_class_reloptions, &fc_isnull);
	if (!fc_isnull)
		fc_index->options = untransformRelOptions(fc_datum);

	/* 如果这是部分索引，反编译并附加谓词 */
	fc_datum = SysCacheGetAttr(INDEXRELID, fc_ht_idx,
							Anum_pg_index_indpred, &fc_isnull);
	if (!fc_isnull)
	{
		char	   *fc_pred_str;
		Node	   *fc_pred_tree;
		bool		fc_found_whole_row;

		/* 将文本字符串转换为节点树 */
		fc_pred_str = TextDatumGetCString(fc_datum);
		fc_pred_tree = (Node *) stringToNode(fc_pred_str);

		/* 调整 Vars 以匹配新表的列编号 */
		fc_pred_tree = map_variable_attnos(fc_pred_tree,
										1, 0,
										fc_attmap,
										InvalidOid, &fc_found_whole_row);

		/* 如同 expandTableLikeClause，拒绝整个行变量 */
		if (fc_found_whole_row)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot convert whole-row table reference"),
					 errdetail("Index \"%s\" contains a whole-row table reference.",
							   RelationGetRelationName(fc_source_idx))));

		fc_index->whereClause = fc_pred_tree;
	}

	/* 清理 */
	ReleaseSysCache(fc_ht_idxrel);
	ReleaseSysCache(fc_ht_am);

	return fc_index;
}

/*
 * 使用来自一个已存在的扩展统计"source_statsid"的信息生成一个
 * CreateStatsStmt 节点，适用于由 heapRel 和 heapRelid 标识的关系。
 *
 * 表达式变量中的属性编号根据 attmap 进行调整。
 */
static CreateStatsStmt *
fc_generateClonedExtStatsStmt(RangeVar *fc_heapRel, Oid fc_heapRelid,
						   Oid fc_source_statsid, const AttrMap *fc_attmap)
{
	HeapTuple	fc_ht_stats;
	Form_pg_statistic_ext fc_statsrec;
	CreateStatsStmt *fc_stats;
	List	   *fc_stat_types = NIL;
	List	   *fc_def_names = NIL;
	bool		fc_isnull;
	Datum		fc_datum;
	ArrayType  *fc_arr;
	char	   *fc_enabled;
	int			fc_i;

	Assert(OidIsValid(fc_heapRelid));
	Assert(fc_heapRel != NULL);

	/*
	 * 获取源统计对象的 pg_statistic_ext 元组。
	 */
	fc_ht_stats = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(fc_source_statsid));
	if (!HeapTupleIsValid(fc_ht_stats))
		elog(ERROR, "cache lookup failed for statistics object %u", fc_source_statsid);
	fc_statsrec = (Form_pg_statistic_ext) GETSTRUCT(fc_ht_stats);

	/* 确定存在的统计类型 */
	fc_datum = SysCacheGetAttr(STATEXTOID, fc_ht_stats,
							Anum_pg_statistic_ext_stxkind, &fc_isnull);
	Assert(!fc_isnull);
	fc_arr = DatumGetArrayTypeP(fc_datum);
	if (ARR_NDIM(fc_arr) != 1 ||
		ARR_HASNULL(fc_arr) ||
		ARR_ELEMTYPE(fc_arr) != CHAROID)
		elog(ERROR, "stxkind is not a 1-D char array");
	fc_enabled = (char *) ARR_DATA_PTR(fc_arr);
	for (fc_i = 0; fc_i < ARR_DIMS(fc_arr)[0]; fc_i++)
	{
		if (fc_enabled[fc_i] == STATS_EXT_NDISTINCT)
			fc_stat_types = lappend(fc_stat_types, makeString("ndistinct"));
		else if (fc_enabled[fc_i] == STATS_EXT_DEPENDENCIES)
			fc_stat_types = lappend(fc_stat_types, makeString("dependencies"));
		else if (fc_enabled[fc_i] == STATS_EXT_MCV)
			fc_stat_types = lappend(fc_stat_types, makeString("mcv"));
		else if (fc_enabled[fc_i] == STATS_EXT_EXPRESSIONS)
			/* 表达式统计对用户不可见 */
			continue;
		else
			elog(ERROR, "unrecognized statistics kind %c", fc_enabled[fc_i]);
	}

	/* 确定统计所涉及的列 */
	for (fc_i = 0; fc_i < fc_statsrec->stxkeys.dim1; fc_i++)
	{
		StatsElem  *fc_selem = makeNode(StatsElem);
		AttrNumber	fc_attnum = fc_statsrec->stxkeys.values[fc_i];

		fc_selem->name = get_attname(fc_heapRelid, fc_attnum, false);
		fc_selem->expr = NULL;

		fc_def_names = lappend(fc_def_names, fc_selem);
	}

	/*
	 * 现在处理表达式（如果有的话）。对于扩展统计，顺序
	 * （相对于常规属性）真正没有关系，因此我们
	 * 只是将它们附加在简单列引用之后。
	 *
	 * XXX 在某些构建/估算的地方将表达式视为属性之前的内容，
	 * 但对于 CREATE 命令这一点完全无关紧要。
	 */
	fc_datum = SysCacheGetAttr(STATEXTOID, fc_ht_stats,
							Anum_pg_statistic_ext_stxexprs, &fc_isnull);

	if (!fc_isnull)
	{
		ListCell   *fc_lc;
		List	   *fc_exprs = NIL;
		char	   *fc_exprsString;

		fc_exprsString = TextDatumGetCString(fc_datum);
		fc_exprs = (List *) stringToNode(fc_exprsString);

		foreach(fc_lc, fc_exprs)
		{
			Node	   *fc_expr = (Node *) lfirst(fc_lc);
			StatsElem  *fc_selem = makeNode(StatsElem);
			bool		fc_found_whole_row;

			/* 调整 Vars 以匹配新表的列编号 */
			fc_expr = map_variable_attnos(fc_expr,
									   1, 0,
									   fc_attmap,
									   InvalidOid,
									   &fc_found_whole_row);

			fc_selem->name = NULL;
			fc_selem->expr = fc_expr;

			fc_def_names = lappend(fc_def_names, fc_selem);
		}

		pfree(fc_exprsString);
	}

	/* 最后，构建输出节点 */
	fc_stats = makeNode(CreateStatsStmt);
	fc_stats->defnames = NULL;
	fc_stats->stat_types = fc_stat_types;
	fc_stats->exprs = fc_def_names;
	fc_stats->relations = list_make1(fc_heapRel);
	fc_stats->stxcomment = NULL;
	fc_stats->transformed = true;	/* 不需要再次 transformStatsStmt */
	fc_stats->if_not_exists = false;

	/* 清理 */
	ReleaseSysCache(fc_ht_stats);

	return fc_stats;
}

/*
 * get_collation		- 获取排序规则的限定名称
 *
 * 如果排序规则为 InvalidOid 或对于给定的 actual_datatype 为默认值，
 * 则返回值为 NIL。
 */
static List * get_collation(Oid fc_collation, Oid fc_actual_datatype)
{
	List	   *fc_result;
	HeapTuple	fc_ht_coll;
	Form_pg_collation fc_coll_rec;
	char	   *fc_nsp_name;
	char	   *fc_coll_name;

	if (!OidIsValid(fc_collation))
		return NIL;				/* 简单的情况 */
	if (fc_collation == get_typcollation(fc_actual_datatype))
		return NIL;				/* 就让它默认 */

	fc_ht_coll = SearchSysCache1(COLLOID, ObjectIdGetDatum(fc_collation));
	if (!HeapTupleIsValid(fc_ht_coll))
		elog(ERROR, "cache lookup failed for collation %u", fc_collation);
	fc_coll_rec = (Form_pg_collation) GETSTRUCT(fc_ht_coll);

	/* 为了简单起见，我们总是对名称进行模式限定 */
	fc_nsp_name = get_namespace_name(fc_coll_rec->collnamespace);
	fc_coll_name = pstrdup(NameStr(fc_coll_rec->collname));
	fc_result = list_make2(makeString(fc_nsp_name), makeString(fc_coll_name));

	ReleaseSysCache(fc_ht_coll);
	return fc_result;
}

/*
 * get_opclass			- 获取索引操作符类的限定名称
 *
 * 如果操作符类是给定 actual_datatype 的默认值，则
 * 返回值为 NIL。
 */
static List * get_opclass(Oid fc_opclass, Oid fc_actual_datatype)
{
	List	   *fc_result = NIL;
	HeapTuple	fc_ht_opc;
	Form_pg_opclass fc_opc_rec;

	fc_ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_opclass));
	if (!HeapTupleIsValid(fc_ht_opc))
		elog(ERROR, "cache lookup failed for opclass %u", fc_opclass);
	fc_opc_rec = (Form_pg_opclass) GETSTRUCT(fc_ht_opc);

	if (GetDefaultOpClass(fc_actual_datatype, fc_opc_rec->opcmethod) != fc_opclass)
	{
		/* 为了简单起见，我们总是对名称进行模式限定 */
		char	   *fc_nsp_name = get_namespace_name(fc_opc_rec->opcnamespace);
		char	   *fc_opc_name = pstrdup(NameStr(fc_opc_rec->opcname));

		fc_result = list_make2(makeString(fc_nsp_name), makeString(fc_opc_name));
	}

	ReleaseSysCache(fc_ht_opc);
	return fc_result;
}


/*
 * transformIndexConstraints
 *		处理 UNIQUE、PRIMARY KEY、EXCLUDE 约束，这些约束会创建索引。
 *		我们还合并了来自 LIKE ... INCLUDING INDEXES 的任何索引定义。
 */
static void fc_transformIndexConstraints(CreateStmtContext *fc_cxt)
{
	IndexStmt  *fc_index;
	List	   *fc_indexlist = NIL;
	List	   *fc_finalindexlist = NIL;
	ListCell   *fc_lc;

	/*
	 * 遍历需要生成索引的约束。对于 PRIMARY
	 * KEY，将每列标记为 NOT NULL 并创建索引。对于 UNIQUE 或
	 * EXCLUDE，创建索引如同 PRIMARY KEY，但不强制要求 NOT
	 * NULL。
	 */
	foreach(fc_lc, fc_cxt->ixconstraints)
	{
		Constraint *fc_constraint = lfirst_node(Constraint, fc_lc);

		Assert(fc_constraint->contype == CONSTR_PRIMARY ||
			   fc_constraint->contype == CONSTR_UNIQUE ||
			   fc_constraint->contype == CONSTR_EXCLUSION);

		fc_index = fc_transformIndexConstraint(fc_constraint, fc_cxt);

		fc_indexlist = lappend(fc_indexlist, fc_index);
	}

	/*
	 * 扫描索引列表并删除任何冗余的索引规范。这可能发生，例如，如果用户写了 UNIQUE PRIMARY KEY。严格解读 SQL 会建议引发错误，但我觉得这太严格了。 - tgl 2001-02-14
	 *
	 * XXX 在 ALTER TABLE 的情况下，查找重复的预先存在的索引也是不错的。
	 */
	if (fc_cxt->pkey != NULL)
	{
		/* 确保我们优先保留 PKEY 索引，而不是其他索引... */
		fc_finalindexlist = list_make1(fc_cxt->pkey);
	}

	foreach(fc_lc, fc_indexlist)
	{
		bool		fc_keep = true;
		ListCell   *fc_k;

		fc_index = lfirst(fc_lc);

		/* 如果是 pkey，它已经在 finalindexlist 中 */
		if (fc_index == fc_cxt->pkey)
			continue;

		foreach(fc_k, fc_finalindexlist)
		{
			IndexStmt  *fc_priorindex = lfirst(fc_k);

			if (equal(fc_index->indexParams, fc_priorindex->indexParams) &&
				equal(fc_index->indexIncludingParams, fc_priorindex->indexIncludingParams) &&
				equal(fc_index->whereClause, fc_priorindex->whereClause) &&
				equal(fc_index->excludeOpNames, fc_priorindex->excludeOpNames) &&
				strcmp(fc_index->accessMethod, fc_priorindex->accessMethod) == 0 &&
				fc_index->nulls_not_distinct == fc_priorindex->nulls_not_distinct &&
				fc_index->deferrable == fc_priorindex->deferrable &&
				fc_index->initdeferred == fc_priorindex->initdeferred)
			{
				fc_priorindex->unique |= fc_index->unique;

				/*
				 * 如果之前的索引尚未命名，而当前的索引是命名的，
				 * 则将名称转移到之前的索引。这确保如果我们有命名和未命名的约束，
				 * 我们将至少使用其中一个名称作为索引。
				 */
				if (fc_priorindex->idxname == NULL)
					fc_priorindex->idxname = fc_index->idxname;
				fc_keep = false;
				break;
			}
		}

		if (fc_keep)
			fc_finalindexlist = lappend(fc_finalindexlist, fc_index);
	}

	/*
	 * 现在将所有 IndexStmts 附加到 cxt->alist。如果我们生成了一个支持主键的 ALTER
	 * TABLE SET NOT NULL 语句，它已经在 cxt->alist 中。
	 */
	fc_cxt->alist = list_concat(fc_cxt->alist, fc_finalindexlist);
}

/*
 * transformIndexConstraint
 *		转换一个 UNIQUE、PRIMARY KEY 或 EXCLUDE 约束以供
 *		transformIndexConstraints 使用。
 *
 * 我们返回一个 IndexStmt。对于 PRIMARY KEY 约束，我们还会生成 NOT NULL 约束，
 * 通过在 cxt->columns 中将 ColumnDefs 标记为 is_not_null，
 * 或通过向 cxt->alist 添加 ALTER TABLE SET NOT NULL 命令。
 */
static IndexStmt *
fc_transformIndexConstraint(Constraint *fc_constraint, CreateStmtContext *fc_cxt)
{
	IndexStmt  *fc_index;
	List	   *fc_notnullcmds = NIL;
	ListCell   *fc_lc;

	fc_index = makeNode(IndexStmt);

	fc_index->unique = (fc_constraint->contype != CONSTR_EXCLUSION);
	fc_index->primary = (fc_constraint->contype == CONSTR_PRIMARY);
	if (fc_index->primary)
	{
		if (fc_cxt->pkey != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("multiple primary keys for table \"%s\" are not allowed",
							fc_cxt->relation->relname),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));
		fc_cxt->pkey = fc_index;

		/*
		 * 在 ALTER TABLE 的情况下，主索引可能已经存在，
		 * 但 DefineIndex 会检查它。
		 */
	}
	fc_index->nulls_not_distinct = fc_constraint->nulls_not_distinct;
	fc_index->isconstraint = true;
	fc_index->deferrable = fc_constraint->deferrable;
	fc_index->initdeferred = fc_constraint->initdeferred;

	if (fc_constraint->conname != NULL)
		fc_index->idxname = pstrdup(fc_constraint->conname);
	else
		fc_index->idxname = NULL;	/* DefineIndex 将选择名称 */

	fc_index->relation = fc_cxt->relation;
	fc_index->accessMethod = fc_constraint->access_method ? fc_constraint->access_method : DEFAULT_INDEX_TYPE;
	fc_index->options = fc_constraint->options;
	fc_index->tableSpace = fc_constraint->indexspace;
	fc_index->whereClause = fc_constraint->where_clause;
	fc_index->indexParams = NIL;
	fc_index->indexIncludingParams = NIL;
	fc_index->excludeOpNames = NIL;
	fc_index->idxcomment = NULL;
	fc_index->indexOid = InvalidOid;
	fc_index->oldNode = InvalidOid;
	fc_index->oldCreateSubid = InvalidSubTransactionId;
	fc_index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
	fc_index->transformed = false;
	fc_index->concurrent = false;
	fc_index->if_not_exists = false;
	fc_index->reset_default_tblspc = fc_constraint->reset_default_tblspc;

	/*
	 * 如果是 ALTER TABLE ADD CONSTRAINT USING INDEX，查找索引并
	 * 验证它是否可用，然后提取隐含的列名列表。（我们在运行时实际上不需要列名列表，
	 * 但现在我们需要它来检查下面的重复列条目。）
	 */
	if (fc_constraint->indexname != NULL)
	{
		char	   *fc_index_name = fc_constraint->indexname;
		Relation	fc_heap_rel = fc_cxt->rel;
		Oid			fc_index_oid;
		Relation	fc_index_rel;
		Form_pg_index fc_index_form;
		oidvector  *fc_indclass;
		Datum		fc_indclassDatum;
		bool		fc_isnull;
		int			fc_i;

		/* 语法不应允许在显式列列表中出现此情况 */
		Assert(fc_constraint->keys == NIL);

		/* 语法仅应允许 PRIMARY 和 UNIQUE 约束 */
		Assert(fc_constraint->contype == CONSTR_PRIMARY ||
			   fc_constraint->contype == CONSTR_UNIQUE);

		/* 必须是 ALTER，而不是 CREATE，但语法并不强制执行该规则 */
		if (!fc_cxt->isalter)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot use an existing index in CREATE TABLE"),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		/* 在与表相同的模式中查找索引 */
		fc_index_oid = get_relname_relid(fc_index_name, RelationGetNamespace(fc_heap_rel));

		if (!OidIsValid(fc_index_oid))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("index \"%s\" does not exist", fc_index_name),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		/* 打开索引（如果它不是索引，将抛出错误） */
		fc_index_rel = index_open(fc_index_oid, AccessShareLock);
		fc_index_form = fc_index_rel->rd_index;

		/* 检查是否没有已关联的约束 */
		if (OidIsValid(get_index_constraint(fc_index_oid)))
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("index \"%s\" is already associated with a constraint",
							fc_index_name),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		/* 对索引执行有效性检查 */
		if (fc_index_form->indrelid != RelationGetRelid(fc_heap_rel))
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("index \"%s\" does not belong to table \"%s\"",
							fc_index_name, RelationGetRelationName(fc_heap_rel)),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		if (!fc_index_form->indisvalid)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("index \"%s\" is not valid", fc_index_name),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		if (!fc_index_form->indisunique)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is not a unique index", fc_index_name),
					 errdetail("Cannot create a primary key or unique constraint using such an index."),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		if (RelationGetIndexExpressions(fc_index_rel) != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("index \"%s\" contains expressions", fc_index_name),
					 errdetail("Cannot create a primary key or unique constraint using such an index."),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		if (RelationGetIndexPredicate(fc_index_rel) != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a partial index", fc_index_name),
					 errdetail("Cannot create a primary key or unique constraint using such an index."),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		/*
		 * 将延迟索引更改为非延迟索引可能不安全。（非约束索引根本无法延迟，
		 * 所以这种情况永远不应发生；没有必要过于担心，但我们来检查一下。）
		 */
		if (!fc_index_form->indimmediate && !fc_constraint->deferrable)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a deferrable index", fc_index_name),
					 errdetail("Cannot create a non-deferrable constraint using a deferrable index."),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		/*
		 * 坚持使用 btree。这是唯一一种支持
		 * 唯一性的类型；但我们必须有一个与普通的 ADD CONSTRAINT 语法完全匹配的索引，
		 * 否则转储和重载将生成不同的索引（特别是破坏 pg_upgrade）。
		 */
		if (fc_index_rel->rd_rel->relam != get_index_am_oid(DEFAULT_INDEX_TYPE, false))
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("index \"%s\" is not a btree", fc_index_name),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		/* 必须以困难的方式获取 indclass */
		fc_indclassDatum = SysCacheGetAttr(INDEXRELID, fc_index_rel->rd_indextuple,
										Anum_pg_index_indclass, &fc_isnull);
		Assert(!fc_isnull);
		fc_indclass = (oidvector *) DatumGetPointer(fc_indclassDatum);

		for (fc_i = 0; fc_i < fc_index_form->indnatts; fc_i++)
		{
			int16		fc_attnum = fc_index_form->indkey.values[fc_i];
			const FormData_pg_attribute *fc_attform;
			char	   *fc_attname;
			Oid			fc_defopclass;

			/*
			 * 我们在这里不应该看到 attnum == 0，因为我们已经拒绝了
			 * 表达式索引。如果我们确实看到，SystemAttributeDefinition 将
			 * 抛出错误。
			 */
			if (fc_attnum > 0)
			{
				Assert(fc_attnum <= fc_heap_rel->rd_att->natts);
				fc_attform = TupleDescAttr(fc_heap_rel->rd_att, fc_attnum - 1);
			}
			else
				fc_attform = SystemAttributeDefinition(fc_attnum);
			fc_attname = pstrdup(NameStr(fc_attform->attname));

			if (fc_i < fc_index_form->indnkeyatts)
			{
				/*
				 * 坚持使用默认的 opclass、排序规则和排序选项。
				 * 虽然索引在非默认设置下仍然可以作为约束使用，
				 * 但它可能不会提供与通常创建的约束完全相同的
				 * 唯一性语义；还有上述提到的转储/重载问题。
				 */
				Datum		fc_attoptions =
				get_attoptions(RelationGetRelid(fc_index_rel), fc_i + 1);

				fc_defopclass = GetDefaultOpClass(fc_attform->atttypid,
											   fc_index_rel->rd_rel->relam);
				if (fc_indclass->values[fc_i] != fc_defopclass ||
					fc_attform->attcollation != fc_index_rel->rd_indcollation[fc_i] ||
					fc_attoptions != (Datum) 0 ||
					fc_index_rel->rd_indoption[fc_i] != 0)
					ereport(ERROR,
							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
							 errmsg("index \"%s\" column number %d does not have default sorting behavior", fc_index_name, fc_i + 1),
							 errdetail("Cannot create a primary key or unique constraint using such an index."),
							 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

				fc_constraint->keys = lappend(fc_constraint->keys, makeString(fc_attname));
			}
			else
				fc_constraint->including = lappend(fc_constraint->including, makeString(fc_attname));
		}

		/* 关闭索引关系但保持锁定 */
		relation_close(fc_index_rel, NoLock);

		fc_index->indexOid = fc_index_oid;
	}

	/*
	 * 如果是 EXCLUDE 约束，语法将返回索引元素和操作符名称的对列表。
	 * 我们必须将其拆分为单独的列表。
	 */
	if (fc_constraint->contype == CONSTR_EXCLUSION)
	{
		foreach(fc_lc, fc_constraint->exclusions)
		{
			List	   *fc_pair = (List *) lfirst(fc_lc);
			IndexElem  *fc_elem;
			List	   *fc_opname;

			Assert(list_length(fc_pair) == 2);
			fc_elem = linitial_node(IndexElem, fc_pair);
			fc_opname = lsecond_node(List, fc_pair);

			fc_index->indexParams = lappend(fc_index->indexParams, fc_elem);
			fc_index->excludeOpNames = lappend(fc_index->excludeOpNames, fc_opname);
		}
	}

	/*
	 * 对于 UNIQUE 和 PRIMARY KEY，我们只有一列列名。
	 *
	 * 确保引用的键存在。如果我们正在创建一个 PRIMARY KEY 索引，
	 * 还要确保它们是 NOT NULL。
	 */
	else
	{
		foreach(fc_lc, fc_constraint->keys)
		{
			char	   *fc_key = strVal(lfirst(fc_lc));
			bool		fc_found = false;
			bool		fc_forced_not_null = false;
			ColumnDef  *fc_column = NULL;
			ListCell   *fc_columns;
			IndexElem  *fc_iparam;

			/* 确保引用的列存在。 */
			foreach(fc_columns, fc_cxt->columns)
			{
				fc_column = lfirst_node(ColumnDef, fc_columns);
				if (strcmp(fc_column->colname, fc_key) == 0)
				{
					fc_found = true;
					break;
				}
			}
			if (fc_found)
			{
				/*
				 * 列在新表中定义。对于 PRIMARY KEY，
				 * 我们可以在这里廉价地施加 NOT NULL 约束…除非
				 * 列被标记为 is_from_type，在这种情况下在这里标记将无效（见 MergeAttributes）。
				 */
				if (fc_constraint->contype == CONSTR_PRIMARY &&
					!fc_column->is_from_type)
				{
					fc_column->is_not_null = true;
					fc_forced_not_null = true;
				}
			}
			else if (SystemAttributeByName(fc_key) != NULL)
			{
				/*
				 * 列将在新表中是系统列，因此接受它。
				 * 系统列永远不能为 null，因此无需担心
				 * PRIMARY/NOT NULL 约束。
				 */
				fc_found = true;
			}
			else if (fc_cxt->inhRelations)
			{
				/* 尝试继承的表 */
				ListCell   *fc_inher;

				foreach(fc_inher, fc_cxt->inhRelations)
				{
					RangeVar   *fc_inh = lfirst_node(RangeVar, fc_inher);
					Relation	fc_rel;
					int			fc_count;

					fc_rel = table_openrv(fc_inh, AccessShareLock);
					/* 检查用户请求的有效关系类型的继承 */
					if (fc_rel->rd_rel->relkind != RELKIND_RELATION &&
						fc_rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
						fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
						ereport(ERROR,
								(errcode(ERRCODE_WRONG_OBJECT_TYPE),
								 errmsg("inherited relation \"%s\" is not a table or foreign table",
										fc_inh->relname)));
					for (fc_count = 0; fc_count < fc_rel->rd_att->natts; fc_count++)
					{
						Form_pg_attribute fc_inhattr = TupleDescAttr(fc_rel->rd_att,
																  fc_count);
						char	   *fc_inhname = NameStr(fc_inhattr->attname);

						if (fc_inhattr->attisdropped)
							continue;
						if (strcmp(fc_key, fc_inhname) == 0)
						{
							fc_found = true;

							/*
							 * 如果父列已经是 NOT NULL，强制设置 forced_not_null 是有诱惑的，
							 * 但这似乎不安全，因为列的 NOT NULL
							 * 标记可能在现在和执行之间消失。做运行时检查以确保安全。
							 */
							break;
						}
					}
					table_close(fc_rel, NoLock);
					if (fc_found)
						break;
				}
			}

			/*
			 * 在 ALTER TABLE 的情况下，不必抱怨没有在命令中创建的索引键；
			 * 它们可能已经存在。
			 * 如果没有，DefineIndex 会对此进行抱怨。
			 */
			if (!fc_found && !fc_cxt->isalter)
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_COLUMN),
						 errmsg("column \"%s\" named in key does not exist", fc_key),
						 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

			/* 检查 PRIMARY KEY(foo, foo) */
			foreach(fc_columns, fc_index->indexParams)
			{
				fc_iparam = (IndexElem *) lfirst(fc_columns);
				if (fc_iparam->name && strcmp(fc_key, fc_iparam->name) == 0)
				{
					if (fc_index->primary)
						ereport(ERROR,
								(errcode(ERRCODE_DUPLICATE_COLUMN),
								 errmsg("column \"%s\" appears twice in primary key constraint",
										fc_key),
								 parser_errposition(fc_cxt->pstate, fc_constraint->location)));
					else
						ereport(ERROR,
								(errcode(ERRCODE_DUPLICATE_COLUMN),
								 errmsg("column \"%s\" appears twice in unique constraint",
										fc_key),
								 parser_errposition(fc_cxt->pstate, fc_constraint->location)));
				}
			}

			/* 好的，将其添加到索引定义中 */
			fc_iparam = makeNode(IndexElem);
			fc_iparam->name = pstrdup(fc_key);
			fc_iparam->expr = NULL;
			fc_iparam->indexcolname = NULL;
			fc_iparam->collation = NIL;
			fc_iparam->opclass = NIL;
			fc_iparam->opclassopts = NIL;
			fc_iparam->ordering = SORTBY_DEFAULT;
			fc_iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
			fc_index->indexParams = lappend(fc_index->indexParams, fc_iparam);

			/*
			 * 对于主键列，如果我们不能通过上面的 is_not_null 确保它，请同时为 ALTER TABLE
			 * SET NOT NULL 创建一个项。
			 */
			if (fc_constraint->contype == CONSTR_PRIMARY && !fc_forced_not_null)
			{
				AlterTableCmd *fc_notnullcmd = makeNode(AlterTableCmd);

				fc_notnullcmd->subtype = AT_SetNotNull;
				fc_notnullcmd->name = pstrdup(fc_key);
				fc_notnullcmds = lappend(fc_notnullcmds, fc_notnullcmd);
			}
		}
	}

	/*
	 * 将包括的列添加到索引定义中。这与上面的
	 * simple-column-name-list 代码非常相似，不同之处在于我们不担心
	 * NOT NULL 标记；主键中的包括列不应该强制为 NOT NULL。
	 * 我们也不会抱怨重复的列，尽管或许我们应该？
	 */
	foreach(fc_lc, fc_constraint->including)
	{
		char	   *fc_key = strVal(lfirst(fc_lc));
		bool		fc_found = false;
		ColumnDef  *fc_column = NULL;
		ListCell   *fc_columns;
		IndexElem  *fc_iparam;

		foreach(fc_columns, fc_cxt->columns)
		{
			fc_column = lfirst_node(ColumnDef, fc_columns);
			if (strcmp(fc_column->colname, fc_key) == 0)
			{
				fc_found = true;
				break;
			}
		}

		if (!fc_found)
		{
			if (SystemAttributeByName(fc_key) != NULL)
			{
				/*
				 * 列将在新表中成为系统列，因此接受
				 * 它。
				 */
				fc_found = true;
			}
			else if (fc_cxt->inhRelations)
			{
				/* 尝试继承的表 */
				ListCell   *fc_inher;

				foreach(fc_inher, fc_cxt->inhRelations)
				{
					RangeVar   *fc_inh = lfirst_node(RangeVar, fc_inher);
					Relation	fc_rel;
					int			fc_count;

					fc_rel = table_openrv(fc_inh, AccessShareLock);
					/* 检查用户请求的有效关系类型的继承 */
					if (fc_rel->rd_rel->relkind != RELKIND_RELATION &&
						fc_rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
						fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
						ereport(ERROR,
								(errcode(ERRCODE_WRONG_OBJECT_TYPE),
								 errmsg("inherited relation \"%s\" is not a table or foreign table",
										fc_inh->relname)));
					for (fc_count = 0; fc_count < fc_rel->rd_att->natts; fc_count++)
					{
						Form_pg_attribute fc_inhattr = TupleDescAttr(fc_rel->rd_att,
																  fc_count);
						char	   *fc_inhname = NameStr(fc_inhattr->attname);

						if (fc_inhattr->attisdropped)
							continue;
						if (strcmp(fc_key, fc_inhname) == 0)
						{
							fc_found = true;
							break;
						}
					}
					table_close(fc_rel, NoLock);
					if (fc_found)
						break;
				}
			}
		}

		/*
		 * 在 ALTER TABLE 情况下，不要抱怨在命令中未创建的索引键；
		 * 它们可能已经存在。DefineIndex
		 * 如果没有会抱怨它们。
		 */
		if (!fc_found && !fc_cxt->isalter)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" named in key does not exist", fc_key),
					 parser_errposition(fc_cxt->pstate, fc_constraint->location)));

		/* 好的，将其添加到索引定义中 */
		fc_iparam = makeNode(IndexElem);
		fc_iparam->name = pstrdup(fc_key);
		fc_iparam->expr = NULL;
		fc_iparam->indexcolname = NULL;
		fc_iparam->collation = NIL;
		fc_iparam->opclass = NIL;
		fc_iparam->opclassopts = NIL;
		fc_index->indexIncludingParams = lappend(fc_index->indexIncludingParams, fc_iparam);
	}

	/*
	 * 如果我们发现任何需要运行时 SET NOT NULL 的内容，请构建一个完整的
	 * ALTER TABLE 命令并将其添加到 cxt->alist。
	 */
	if (fc_notnullcmds)
	{
		AlterTableStmt *fc_alterstmt = makeNode(AlterTableStmt);

		fc_alterstmt->relation = copyObject(fc_cxt->relation);
		fc_alterstmt->cmds = fc_notnullcmds;
		fc_alterstmt->objtype = OBJECT_TABLE;
		fc_alterstmt->missing_ok = false;

		fc_cxt->alist = lappend(fc_cxt->alist, fc_alterstmt);
	}

	return fc_index;
}

/*
 * transformCheckConstraints
 *	处理 CHECK 约束
 *
 * 现在，从 ALTER TABLE 调用时这里没有任何操作，
 * 但其他约束转换函数在 CREATE TABLE 和 ALTER TABLE 路径中都被调用，
 * 所以这里也一样，只要我们没有被授权跳过验证就不执行任何操作。
 */
static void fc_transformCheckConstraints(CreateStmtContext *fc_cxt, bool fc_skipValidation)
{
	ListCell   *fc_ckclist;

	if (fc_cxt->ckconstraints == NIL)
		return;

	/*
	 * 如果创建新表（但不是外部表），我们可以安全地跳过
	 * CHECK 约束的验证，并且仍然将它们标记为有效。（这将覆盖任何用户提供的 NOT VALID 标志。）
	 */
	if (fc_skipValidation)
	{
		foreach(fc_ckclist, fc_cxt->ckconstraints)
		{
			Constraint *fc_constraint = (Constraint *) lfirst(fc_ckclist);

			fc_constraint->skip_validation = true;
			fc_constraint->initially_valid = true;
		}
	}
}

/*
 * transformFKConstraints
 *	处理 FOREIGN KEY 约束
 */
static void fc_transformFKConstraints(CreateStmtContext *fc_cxt,
					   bool fc_skipValidation, bool fc_isAddConstraint)
{
	ListCell   *fc_fkclist;

	if (fc_cxt->fkconstraints == NIL)
		return;

	/*
	 * 如果是 CREATE TABLE 或添加具有 NULL 默认值的列，我们可以安全地
	 * 跳过 FK 约束的验证，并且仍然将它们标记为有效。
	 * （这将覆盖任何用户提供的 NOT VALID 标志。）
	 */
	if (fc_skipValidation)
	{
		foreach(fc_fkclist, fc_cxt->fkconstraints)
		{
			Constraint *fc_constraint = (Constraint *) lfirst(fc_fkclist);

			fc_constraint->skip_validation = true;
			fc_constraint->initially_valid = true;
		}
	}

	/*
	 * 对于 CREATE TABLE 或 ALTER TABLE ADD COLUMN，准备一个 ALTER TABLE ADD
	 * CONSTRAINT 命令，以在基本命令完成后执行。（如果
	 * 从 ADD CONSTRAINT 调用，则该例程将把 FK 约束添加到
	 * 自己的子命令列表中。）
	 *
	 * 注意：ADD CONSTRAINT 命令必须在任何索引
	 * 创建命令之后执行。因此，这应在
	 * transformIndexConstraints 之后运行，以便 CREATE INDEX 命令
	 * 已经在 cxt->alist 中。另见 cxt->likeclauses 的处理。
	 */
	if (!fc_isAddConstraint)
	{
		AlterTableStmt *fc_alterstmt = makeNode(AlterTableStmt);

		fc_alterstmt->relation = fc_cxt->relation;
		fc_alterstmt->cmds = NIL;
		fc_alterstmt->objtype = OBJECT_TABLE;

		foreach(fc_fkclist, fc_cxt->fkconstraints)
		{
			Constraint *fc_constraint = (Constraint *) lfirst(fc_fkclist);
			AlterTableCmd *fc_altercmd = makeNode(AlterTableCmd);

			fc_altercmd->subtype = AT_AddConstraint;
			fc_altercmd->name = NULL;
			fc_altercmd->def = (Node *) fc_constraint;
			fc_alterstmt->cmds = lappend(fc_alterstmt->cmds, fc_altercmd);
		}

		fc_cxt->alist = lappend(fc_cxt->alist, fc_alterstmt);
	}
}

/*
 * transformIndexStmt - 解析分析 CREATE INDEX 和 ALTER TABLE
 *
 * 注意：对于不使用索引表达式或
 * 谓词表达式的索引，这就是一个无操作。 有几条代码路径创建索引
 * 而不麻烦地调用这个，因为它们知道自己没有
 * 需要处理的任何这样的表达式。
 *
 * 为了避免竞争条件，这个函数只依赖于
 * 传入的 relid（而不是 stmt->relation）来确定目标
 * 关系，这一点很重要。
 */
IndexStmt *
transformIndexStmt(Oid fc_relid, IndexStmt *fc_stmt, const char *fc_queryString)
{
	ParseState *fc_pstate;
	ParseNamespaceItem *fc_nsitem;
	ListCell   *fc_l;
	Relation	fc_rel;

	/* 如果声明已经被转换，则没有任何操作。 */
	if (fc_stmt->transformed)
		return fc_stmt;

	/* 设置 pstate */
	fc_pstate = make_parsestate(NULL);
	fc_pstate->p_sourcetext = fc_queryString;

	/*
	 * 将父表放入 rtable，以便表达式可以不带限定符地引用
	 * 它的字段。调用者负责锁定
	 * 关系，但我们仍然需要打开它。
	 */
	fc_rel = relation_open(fc_relid, NoLock);
	fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_rel,
										   AccessShareLock,
										   NULL, false, true);

	/* 不需要加入列表，是的到命名空间 */
	addNSItemToQuery(fc_pstate, fc_nsitem, false, true, true);

	/* 处理 where 子句 */
	if (fc_stmt->whereClause)
	{
		fc_stmt->whereClause = transformWhereClause(fc_pstate,
												 fc_stmt->whereClause,
												 EXPR_KIND_INDEX_PREDICATE,
												 "WHERE");
		/* 我们还必须修复它的排序规则 */
		assign_expr_collations(fc_pstate, fc_stmt->whereClause);
	}

	/* 处理任何索引表达式 */
	foreach(fc_l, fc_stmt->indexParams)
	{
		IndexElem  *fc_ielem = (IndexElem *) lfirst(fc_l);

		if (fc_ielem->expr)
		{
			/* 在转换表达式之前提取初步索引列名 */
			if (fc_ielem->indexcolname == NULL)
				fc_ielem->indexcolname = FigureIndexColname(fc_ielem->expr);

			/* 现在对表达式进行解析转换 */
			fc_ielem->expr = transformExpr(fc_pstate, fc_ielem->expr,
										EXPR_KIND_INDEX_EXPRESSION);

			/* 我们也必须修复它的排序规则 */
			assign_expr_collations(fc_pstate, fc_ielem->expr);

			/*
			 * transformExpr() 应该已经根据 EXPR_KIND_
			 * 拒绝了子查询、聚合、窗口函数和 SRF，
			 * 以用于索引表达式。
			 *
			 * DefineIndex() 将进行更多检查。
			 */
		}
	}

	/*
	 * 检查只提到基础关系。（现在这应该是死代码
	 * 因为 add_missing_from 已经被淘汰。）
	 */
	if (list_length(fc_pstate->p_rtable) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
				 errmsg("index expressions and predicates can refer only to the table being indexed")));

	free_parsestate(fc_pstate);

	/* 关闭关系 */
	table_close(fc_rel, NoLock);

	/* 将声明标记为已成功转换 */
	fc_stmt->transformed = true;

	return fc_stmt;
}

/*
 * transformStatsStmt - 解析分析 CREATE STATISTICS
 *
 * 为了避免竞争条件，这个函数只依赖于
 * 传入的 relid（而不是 stmt->relation）来确定目标
 * 关系，这一点很重要。
 */
CreateStatsStmt *
transformStatsStmt(Oid fc_relid, CreateStatsStmt *fc_stmt, const char *fc_queryString)
{
	ParseState *fc_pstate;
	ParseNamespaceItem *fc_nsitem;
	ListCell   *fc_l;
	Relation	fc_rel;

	/* 如果声明已经被转换，则没有任何操作。 */
	if (fc_stmt->transformed)
		return fc_stmt;

	/* 设置 pstate */
	fc_pstate = make_parsestate(NULL);
	fc_pstate->p_sourcetext = fc_queryString;

	/*
	 * 将父表放入 rtable，以便表达式可以不带限定符地引用
	 * 它的字段。调用者负责锁定
	 * 关系，但我们仍然需要打开它。
	 */
	fc_rel = relation_open(fc_relid, NoLock);
	fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_rel,
										   AccessShareLock,
										   NULL, false, true);

	/* 不需要加入列表，是的到命名空间 */
	addNSItemToQuery(fc_pstate, fc_nsitem, false, true, true);

	/* 处理任何表达式 */
	foreach(fc_l, fc_stmt->exprs)
	{
		StatsElem  *fc_selem = (StatsElem *) lfirst(fc_l);

		if (fc_selem->expr)
		{
			/* 现在对表达式进行解析转换 */
			fc_selem->expr = transformExpr(fc_pstate, fc_selem->expr,
										EXPR_KIND_STATS_EXPRESSION);

			/* 我们也必须修复它的排序规则 */
			assign_expr_collations(fc_pstate, fc_selem->expr);
		}
	}

	/*
	 * 检查只提到基础关系。（现在这应该是死代码
	 * 因为 add_missing_from 已经被淘汰。）
	 */
	if (list_length(fc_pstate->p_rtable) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
				 errmsg("statistics expressions can refer only to the table being referenced")));

	free_parsestate(fc_pstate);

	/* 关闭关系 */
	table_close(fc_rel, NoLock);

	/* 将声明标记为已成功转换 */
	fc_stmt->transformed = true;

	return fc_stmt;
}


/*
 * transformRuleStmt -
 *	  转换 CREATE RULE 语句。操作是一个解析树的列表，
 *	  被转换为查询树的列表，我们也将
 *	  如果有的话，转换 WHERE 子句。
 *
 * actions 和 whereClause 是接收
 * 转换结果的输出参数。
 */
void transformRuleStmt(RuleStmt *fc_stmt, const char *fc_queryString,
				  List **fc_actions, Node **fc_whereClause)
{
	Relation	fc_rel;
	ParseState *fc_pstate;
	ParseNamespaceItem *fc_oldnsitem;
	ParseNamespaceItem *fc_newnsitem;

	/*
	 * 为了避免死锁，确保我们首先做的事情是获取
	 * AccessExclusiveLock 在目标关系上。这将被 DefineQueryRewrite() 所需，
	 * 我们不希望提前获取较小的锁。
	 */
	fc_rel = table_openrv(fc_stmt->relation, AccessExclusiveLock);

	if (fc_rel->rd_rel->relkind == RELKIND_MATVIEW)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("rules on materialized views are not supported")));

	/* 设置 pstate */
	fc_pstate = make_parsestate(NULL);
	fc_pstate->p_sourcetext = fc_queryString;

	
/*
	 * 注意：'OLD' 的 varno 必须始终等于 1，'NEW' 必须等于 2。
	 * 在主 pstate 中设置它们的 ParseNamespaceItems，以便在解析规则资格时使用。
	 */
	fc_oldnsitem = addRangeTableEntryForRelation(fc_pstate, fc_rel,
											  AccessShareLock,
											  makeAlias("old", NIL),
											  false, false);
	fc_newnsitem = addRangeTableEntryForRelation(fc_pstate, fc_rel,
											  AccessShareLock,
											  makeAlias("new", NIL),
											  false, false);
	/* 必须重写 addRangeTableEntry 的默认访问检查标志 */
	fc_oldnsitem->p_rte->requiredPerms = 0;
	fc_newnsitem->p_rte->requiredPerms = 0;

	/*
	 * 它们也必须在命名空间中以便于查找，但只添加当前规则类型相关的一个。
	 * 在 UPDATE 规则中，条件必须引用 OLD.field 或 NEW.field，以避免歧义，但在 INSERT 和 DELETE 中不需要这么严格。
	 * 我们不将它们添加到连接列表中。
	 */
	switch (fc_stmt->event)
	{
		case CMD_SELECT:
			addNSItemToQuery(fc_pstate, fc_oldnsitem, false, true, true);
			break;
		case CMD_UPDATE:
			addNSItemToQuery(fc_pstate, fc_oldnsitem, false, true, true);
			addNSItemToQuery(fc_pstate, fc_newnsitem, false, true, true);
			break;
		case CMD_INSERT:
			addNSItemToQuery(fc_pstate, fc_newnsitem, false, true, true);
			break;
		case CMD_DELETE:
			addNSItemToQuery(fc_pstate, fc_oldnsitem, false, true, true);
			break;
		default:
			elog(ERROR, "unrecognized event type: %d",
				 (int) fc_stmt->event);
			break;
	}

	/* 处理 where 子句 */
	*fc_whereClause = transformWhereClause(fc_pstate,
										fc_stmt->whereClause,
										EXPR_KIND_WHERE,
										"WHERE");
	/* 我们还必须修复它的排序规则 */
	assign_expr_collations(fc_pstate, *fc_whereClause);

	/* 这可能是没有 add_missing_from 的死代码 */
	if (list_length(fc_pstate->p_rtable) != 2) /* 淘气，淘气... */
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("rule WHERE condition cannot contain references to other relations")));

	/*
	 * 带有资格的“没有”的规则需要一个查询的 rangetable，以便
	 * 重写处理器可以将否定的规则资格添加到
	 * 原始查询。我们在这里创建一个具有新命令类型 CMD_NOTHING 的查询，
	 * 它在重写系统中被特殊处理。
	 */
	if (fc_stmt->actions == NIL)
	{
		Query	   *fc_nothing_qry = makeNode(Query);

		fc_nothing_qry->commandType = CMD_NOTHING;
		fc_nothing_qry->rtable = fc_pstate->p_rtable;
		fc_nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* 不需要连接 */

		*fc_actions = list_make1(fc_nothing_qry);
	}
	else
	{
		ListCell   *fc_l;
		List	   *fc_newactions = NIL;

		/*
		 * 转换每个语句，就像 parse_sub_analyze() 一样
		 */
		foreach(fc_l, fc_stmt->actions)
		{
			Node	   *fc_action = (Node *) lfirst(fc_l);
			ParseState *fc_sub_pstate = make_parsestate(NULL);
			Query	   *fc_sub_qry,
					   *fc_top_subqry;
			bool		fc_has_old,
						fc_has_new;

			/*
			 * 由于外部 ParseState 不是内部的父级，必须手动传递
			 * 查询文本。
			 */
			fc_sub_pstate->p_sourcetext = fc_queryString;

			/*
			 * 为该语句在 rtable 中设置 OLD/NEW。条目
			 * 仅添加到 relnamespace，而不是 varnamespace，因为我们
			 * 不希望它们被不合格的字段名或
			 * 在规则操作中引用“*”。我们稍后决定是否将它们放入
			 * joinlist 中。
			 */
			fc_oldnsitem = addRangeTableEntryForRelation(fc_sub_pstate, fc_rel,
													  AccessShareLock,
													  makeAlias("old", NIL),
													  false, false);
			fc_newnsitem = addRangeTableEntryForRelation(fc_sub_pstate, fc_rel,
													  AccessShareLock,
													  makeAlias("new", NIL),
													  false, false);
			fc_oldnsitem->p_rte->requiredPerms = 0;
			fc_newnsitem->p_rte->requiredPerms = 0;
			addNSItemToQuery(fc_sub_pstate, fc_oldnsitem, false, true, false);
			addNSItemToQuery(fc_sub_pstate, fc_newnsitem, false, true, false);

			/* 转换规则动作语句 */
			fc_top_subqry = transformStmt(fc_sub_pstate, fc_action);

			/*
			 * 我们不能支持具有非空规则 WHERE 条件的实用程序语句动作（例如 NOTIFY），
			 * 因为没有办法使
			 * 实用程序动作有条件地执行。
			 */
			if (fc_top_subqry->commandType == CMD_UTILITY &&
				*fc_whereClause != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("rules with WHERE conditions can only have SELECT, INSERT, UPDATE, or DELETE actions")));

			/*
			 * 如果动作是 INSERT...SELECT，OLD/NEW 已被推送到
			 * SELECT 中，这就是我们需要查看的内容。（肮脏的
			 * 解决方案...尝试在重新设计查询树时解决这个问题。）
			 */
			fc_sub_qry = getInsertSelectQuery(fc_top_subqry, NULL);

			/*
			 * 如果 sub_qry 是一个集合运算，我们不能附加任何资格
			 * 到它，因为规划器不会注意到它们。或许
			 * 有一天可以放宽这一点，但现在，我们不妨立即拒绝
			 * 这样的规则。
			 */
			if (fc_sub_qry->setOperations != NULL && *fc_whereClause != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));

			/*
			 * 验证动作对 OLD/NEW 的使用，资格也一样
			 */
			fc_has_old =
				rangeTableEntry_used((Node *) fc_sub_qry, PRS2_OLD_VARNO, 0) ||
				rangeTableEntry_used(*fc_whereClause, PRS2_OLD_VARNO, 0);
			fc_has_new =
				rangeTableEntry_used((Node *) fc_sub_qry, PRS2_NEW_VARNO, 0) ||
				rangeTableEntry_used(*fc_whereClause, PRS2_NEW_VARNO, 0);

			switch (fc_stmt->event)
			{
				case CMD_SELECT:
					if (fc_has_old)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("ON SELECT rule cannot use OLD")));
					if (fc_has_new)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("ON SELECT rule cannot use NEW")));
					break;
				case CMD_UPDATE:
					/* 两者都可以 */
					break;
				case CMD_INSERT:
					if (fc_has_old)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("ON INSERT rule cannot use OLD")));
					break;
				case CMD_DELETE:
					if (fc_has_new)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("ON DELETE rule cannot use NEW")));
					break;
				default:
					elog(ERROR, "unrecognized event type: %d",
						 (int) fc_stmt->event);
					break;
			}

			/*
			 * 在 WITH 查询中不允许使用 OLD/NEW，因为它们将
			 * 变成 WITH 的外部引用，这是我们不允许的。
			 * 但是，在我们分析查询时，它们已经在外部 rangetable 中，因此我们
			 * 必须进行检查。
			 *
			 * 注意，在 INSERT...SELECT 的情况下，我们需要检查
			 * top_subqry 和 sub_qry 的 CTE 列表。
			 *
			 * 注意，我们没有深入查询主体查找
			 * 嵌套子 SELECT 中的 WITH。那里的一个 WITH 可以
			 * 合理地引用 OLD/NEW，因为它会是一个
			 * 间接相关的外部引用。
			 */
			if (rangeTableEntry_used((Node *) fc_top_subqry->cteList,
									 PRS2_OLD_VARNO, 0) ||
				rangeTableEntry_used((Node *) fc_sub_qry->cteList,
									 PRS2_OLD_VARNO, 0))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot refer to OLD within WITH query")));
			if (rangeTableEntry_used((Node *) fc_top_subqry->cteList,
									 PRS2_NEW_VARNO, 0) ||
				rangeTableEntry_used((Node *) fc_sub_qry->cteList,
									 PRS2_NEW_VARNO, 0))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot refer to NEW within WITH query")));

			/*
			 * 为了效率，只有在 OLD 实际上在语句或资格中被引用时，
			 * 才将 OLD 添加到规则动作的 jointree 中。
			 *
			 * 对于 INSERT，NEW 实际上不是一个关系（只是一个对
			 * 将要插入的元组的引用），不应添加到
			 * jointree 中。
			 *
			 * 对于 UPDATE，我们将 NEW 视为对
			 * OLD 的另一种引用，因为它代表对现有关系的
			 * *转变* 元组的引用。单独将 NEW 输入到
			 * jointree 中是错误的，因为这会导致对更新关系的双重
			 * 连接。如果仅提到 NEW 而不提到 OLD，未能创建
			 * jointree 条目也是错误的。
			 */
			if (fc_has_old || (fc_has_new && fc_stmt->event == CMD_UPDATE))
			{
				RangeTblRef *fc_rtr;

				/*
				 * 如果 sub_qry 是一个集合运算，操作它的 jointree 将毫无
				 * 作用，因为 jointree 是虚假的。（由于先前的测试，这应该是
				 * 绝对不会发生的情况。）
				 */
				if (fc_sub_qry->setOperations != NULL)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
				/* 以黑客方式将 OLD 添加到已构建的 FROM 子句中 */
				fc_rtr = makeNode(RangeTblRef);
				fc_rtr->rtindex = fc_oldnsitem->p_rtindex;
				fc_sub_qry->jointree->fromlist =
					lappend(fc_sub_qry->jointree->fromlist, fc_rtr);
			}

			fc_newactions = lappend(fc_newactions, fc_top_subqry);

			free_parsestate(fc_sub_pstate);
		}

		*fc_actions = fc_newactions;
	}

	free_parsestate(fc_pstate);

	/* 关闭关系，但保留独占锁 */
	table_close(fc_rel, NoLock);
}


/*
 * transformAlterTableStmt -
 *	对 ALTER TABLE 进行解析分析
 *
 * 返回转换后的 AlterTableStmt。在转换后的语句之前和之后可能会有其他的操作，
 * 这些操作以 *beforeStmts 和 *afterStmts 的公用命令解析树列表形式返回。
 *
 * 为了避免竞争条件，重要的是这个函数只依赖于
 * 传入的 relid（而不是 stmt->relation）来确定目标
 * 关系。
 */
AlterTableStmt *
transformAlterTableStmt(Oid fc_relid, AlterTableStmt *fc_stmt,
						const char *fc_queryString,
						List **fc_beforeStmts, List **fc_afterStmts)
{
	Relation	fc_rel;
	TupleDesc	fc_tupdesc;
	ParseState *fc_pstate;
	CreateStmtContext fc_cxt;
	List	   *fc_save_alist;
	ListCell   *fc_lcmd,
			   *fc_l;
	List	   *fc_newcmds = NIL;
	bool		fc_skipValidation = true;
	AlterTableCmd *fc_newcmd;
	ParseNamespaceItem *fc_nsitem;

	/* 调用者负责锁定关系 */
	fc_rel = relation_open(fc_relid, NoLock);
	fc_tupdesc = RelationGetDescr(fc_rel);

	/* 设置 pstate */
	fc_pstate = make_parsestate(NULL);
	fc_pstate->p_sourcetext = fc_queryString;
	fc_nsitem = addRangeTableEntryForRelation(fc_pstate,
										   fc_rel,
										   AccessShareLock,
										   NULL,
										   false,
										   true);
	addNSItemToQuery(fc_pstate, fc_nsitem, false, true, true);

	/* 设置 CreateStmtContext */
	fc_cxt.pstate = fc_pstate;
	if (fc_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
	{
		fc_cxt.stmtType = "ALTER FOREIGN TABLE";
		fc_cxt.isforeign = true;
	}
	else
	{
		fc_cxt.stmtType = "ALTER TABLE";
		fc_cxt.isforeign = false;
	}
	fc_cxt.relation = fc_stmt->relation;
	fc_cxt.rel = fc_rel;
	fc_cxt.inhRelations = NIL;
	fc_cxt.isalter = true;
	fc_cxt.columns = NIL;
	fc_cxt.ckconstraints = NIL;
	fc_cxt.fkconstraints = NIL;
	fc_cxt.ixconstraints = NIL;
	fc_cxt.likeclauses = NIL;
	fc_cxt.blist = NIL;
	fc_cxt.alist = NIL;
	fc_cxt.pkey = NULL;
	fc_cxt.ispartitioned = (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
	fc_cxt.partbound = NULL;
	fc_cxt.ofType = false;

	/*
	 * 转换需要的 ALTER 子命令（大部分不需要）。这些大量
	 * 复用来自 CREATE TABLE 的代码。
	 */
	foreach(fc_lcmd, fc_stmt->cmds)
	{
		AlterTableCmd *fc_cmd = (AlterTableCmd *) lfirst(fc_lcmd);

		switch (fc_cmd->subtype)
		{
			case AT_AddColumn:
			case AT_AddColumnRecurse:
				{
					ColumnDef  *fc_def = castNode(ColumnDef, fc_cmd->def);

					fc_transformColumnDefinition(&fc_cxt, fc_def);

					/*
					 * 如果列有非空默认值，我们不能跳过
					 * 外键的验证。
					 */
					if (fc_def->raw_default != NULL)
						fc_skipValidation = false;

					/*
					 * 所有约束以其他方式处理。删除原始列表
					 */
					fc_def->constraints = NIL;

					fc_newcmds = lappend(fc_newcmds, fc_cmd);
					break;
				}

			case AT_AddConstraint:
			case AT_AddConstraintRecurse:

				/*
				 * 原始的 AddConstraint cmd 节点不会转到 newcmds
				 */
				if (IsA(fc_cmd->def, Constraint))
				{
					fc_transformTableConstraint(&fc_cxt, (Constraint *) fc_cmd->def);
					if (((Constraint *) fc_cmd->def)->contype == CONSTR_FOREIGN)
						fc_skipValidation = false;
				}
				else
					elog(ERROR, "unrecognized node type: %d",
						 (int) nodeTag(fc_cmd->def));
				break;

			case AT_AlterColumnType:
				{
					ColumnDef  *fc_def = castNode(ColumnDef, fc_cmd->def);
					AttrNumber	fc_attnum;

					/*
					 * 对于 ALTER COLUMN TYPE，如果指定了 USING 子句，则进行转换。
					 */
					if (fc_def->raw_default)
					{
						fc_def->cooked_default =
							transformExpr(fc_pstate, fc_def->raw_default,
										  EXPR_KIND_ALTER_COL_TRANSFORM);
					}

					/*
					 * 对于身份列，创建 ALTER SEQUENCE 命令以
					 * 更改序列的数据类型。
					 */
					fc_attnum = get_attnum(fc_relid, fc_cmd->name);
					if (fc_attnum == InvalidAttrNumber)
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_COLUMN),
								 errmsg("column \"%s\" of relation \"%s\" does not exist",
										fc_cmd->name, RelationGetRelationName(fc_rel))));

					if (fc_attnum > 0 &&
						TupleDescAttr(fc_tupdesc, fc_attnum - 1)->attidentity)
					{
						Oid			fc_seq_relid = getIdentitySequence(fc_relid, fc_attnum, false);
						Oid			fc_typeOid = typenameTypeId(fc_pstate, fc_def->typeName);
						AlterSeqStmt *fc_altseqstmt = makeNode(AlterSeqStmt);

						fc_altseqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(fc_seq_relid)),
															get_rel_name(fc_seq_relid),
															-1);
						fc_altseqstmt->options = list_make1(makeDefElem("as", (Node *) makeTypeNameFromOid(fc_typeOid, -1), -1));
						fc_altseqstmt->for_identity = true;
						fc_cxt.blist = lappend(fc_cxt.blist, fc_altseqstmt);
					}

					fc_newcmds = lappend(fc_newcmds, fc_cmd);
					break;
				}

			case AT_AddIdentity:
				{
					Constraint *fc_def = castNode(Constraint, fc_cmd->def);
					ColumnDef  *fc_newdef = makeNode(ColumnDef);
					AttrNumber	fc_attnum;

					fc_newdef->colname = fc_cmd->name;
					fc_newdef->identity = fc_def->generated_when;
					fc_cmd->def = (Node *) fc_newdef;

					fc_attnum = get_attnum(fc_relid, fc_cmd->name);
					if (fc_attnum == InvalidAttrNumber)
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_COLUMN),
								 errmsg("column \"%s\" of relation \"%s\" does not exist",
										fc_cmd->name, RelationGetRelationName(fc_rel))));

					fc_generateSerialExtraStmts(&fc_cxt, fc_newdef,
											 get_atttype(fc_relid, fc_attnum),
											 fc_def->options, true, true,
											 NULL, NULL);

					fc_newcmds = lappend(fc_newcmds, fc_cmd);
					break;
				}

			case AT_SetIdentity:
				{
					/*
					 * 为身份列的内部序列创建 ALTER SEQUENCE 语句。
					 */
					ListCell   *fc_lc;
					List	   *fc_newseqopts = NIL;
					List	   *fc_newdef = NIL;
					AttrNumber	fc_attnum;
					Oid			fc_seq_relid;

					/*
					 * 将选项拆分为 ALTER SEQUENCE 处理的选项和
					 * ALTER TABLE 正确的选项。
					 */
					foreach(fc_lc, castNode(List, fc_cmd->def))
					{
						DefElem    *fc_def = lfirst_node(DefElem, fc_lc);

						if (strcmp(fc_def->defname, "generated") == 0)
							fc_newdef = lappend(fc_newdef, fc_def);
						else
							fc_newseqopts = lappend(fc_newseqopts, fc_def);
					}

					fc_attnum = get_attnum(fc_relid, fc_cmd->name);
					if (fc_attnum == InvalidAttrNumber)
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_COLUMN),
								 errmsg("column \"%s\" of relation \"%s\" does not exist",
										fc_cmd->name, RelationGetRelationName(fc_rel))));

					fc_seq_relid = getIdentitySequence(fc_relid, fc_attnum, true);

					if (fc_seq_relid)
					{
						AlterSeqStmt *fc_seqstmt;

						fc_seqstmt = makeNode(AlterSeqStmt);
						fc_seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(fc_seq_relid)),
														 get_rel_name(fc_seq_relid), -1);
						fc_seqstmt->options = fc_newseqopts;
						fc_seqstmt->for_identity = true;
						fc_seqstmt->missing_ok = false;

						fc_cxt.blist = lappend(fc_cxt.blist, fc_seqstmt);
					}

					
/*
					 * 如果列不是身份列，我们只是让
					 * ALTER TABLE 命令稍后出错。 （有些情况
					 * 这未能覆盖，但我们需要重构
					 * 在我们能够修复之前，序列依赖链接的创建发生的位置。）
					 */

					fc_cmd->def = (Node *) fc_newdef;
					fc_newcmds = lappend(fc_newcmds, fc_cmd);
					break;
				}

			case AT_AttachPartition:
			case AT_DetachPartition:
				{
					PartitionCmd *fc_partcmd = (PartitionCmd *) fc_cmd->def;

					fc_transformPartitionCmd(&fc_cxt, fc_partcmd);
					/* 分配分区边界的变换值 */
					fc_partcmd->bound = fc_cxt.partbound;
				}

				fc_newcmds = lappend(fc_newcmds, fc_cmd);
				break;

			default:

				/*
				 * 目前，对于不需要转换的子命令类型，我们实际上不应该到达这里；但如果到达了，只需保持不变地发出它们。
				 */
				fc_newcmds = lappend(fc_newcmds, fc_cmd);
				break;
		}
	}

	/*
	 * 将 cxt.alist 中现有的任何内容转移到 save_alist，以便将其与 transformIndexConstraints 的输出分开。
	 */
	fc_save_alist = fc_cxt.alist;
	fc_cxt.alist = NIL;

	/* 后处理约束 */
	fc_transformIndexConstraints(&fc_cxt);
	fc_transformFKConstraints(&fc_cxt, fc_skipValidation, true);
	fc_transformCheckConstraints(&fc_cxt, false);

	/*
	 * 将任何索引创建命令推入 ALTER，以便 tablecmds.c 可以很好地进行调度。请注意，tablecmds.c 假定附加到 AT_AddIndex 或 AT_AddIndexConstraint 子命令的 IndexStmt 已经通过 transformIndexStmt 处理过。
	 */
	foreach(fc_l, fc_cxt.alist)
	{
		Node	   *fc_istmt = (Node *) lfirst(fc_l);

		/*
		 * 我们在这里假设 cxt.alist 只包含 IndexStmts 和可能从主键约束生成的 ALTER TABLE SET NOT NULL 语句。我们直接吸收后者的子命令。
		 */
		if (IsA(fc_istmt, IndexStmt))
		{
			IndexStmt  *fc_idxstmt = (IndexStmt *) fc_istmt;

			fc_idxstmt = transformIndexStmt(fc_relid, fc_idxstmt, fc_queryString);
			fc_newcmd = makeNode(AlterTableCmd);
			fc_newcmd->subtype = OidIsValid(fc_idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
			fc_newcmd->def = (Node *) fc_idxstmt;
			fc_newcmds = lappend(fc_newcmds, fc_newcmd);
		}
		else if (IsA(fc_istmt, AlterTableStmt))
		{
			AlterTableStmt *fc_alterstmt = (AlterTableStmt *) fc_istmt;

			fc_newcmds = list_concat(fc_newcmds, fc_alterstmt->cmds);
		}
		else
			elog(ERROR, "unexpected stmt type %d", (int) nodeTag(fc_istmt));
	}
	fc_cxt.alist = NIL;

	/* 将任何 CHECK 或 FK 约束附加到命令列表 */
	foreach(fc_l, fc_cxt.ckconstraints)
	{
		fc_newcmd = makeNode(AlterTableCmd);
		fc_newcmd->subtype = AT_AddConstraint;
		fc_newcmd->def = (Node *) lfirst(fc_l);
		fc_newcmds = lappend(fc_newcmds, fc_newcmd);
	}
	foreach(fc_l, fc_cxt.fkconstraints)
	{
		fc_newcmd = makeNode(AlterTableCmd);
		fc_newcmd->subtype = AT_AddConstraint;
		fc_newcmd->def = (Node *) lfirst(fc_l);
		fc_newcmds = lappend(fc_newcmds, fc_newcmd);
	}

	/* 关闭关系 */
	relation_close(fc_rel, NoLock);

	/*
	 * 输出结果。
	 */
	fc_stmt->cmds = fc_newcmds;

	*fc_beforeStmts = fc_cxt.blist;
	*fc_afterStmts = list_concat(fc_cxt.alist, fc_save_alist);

	return fc_stmt;
}


/*
 * 预处理列约束子句列表，将约束属性附加到其主约束节点，并检测不一致/错误放置的约束属性。
 *
 * 注意：目前，仅支持 FOREIGN KEY、UNIQUE、EXCLUSION 和 PRIMARY KEY 约束的属性，但将来应该支持其他约束类型。
 */
static void fc_transformConstraintAttrs(CreateStmtContext *fc_cxt, List *fc_constraintList)
{
	Constraint *fc_lastprimarycon = NULL;
	bool		fc_saw_deferrability = false;
	bool		fc_saw_initially = false;
	ListCell   *fc_clist;

#define SUPPORTS_ATTRS(node)				\
	((node) != NULL &&						\
	 ((node)->contype == CONSTR_PRIMARY ||	\
	  (node)->contype == CONSTR_UNIQUE ||	\
	  (node)->contype == CONSTR_EXCLUSION || \
	  (node)->contype == CONSTR_FOREIGN))

	foreach(fc_clist, fc_constraintList)
	{
		Constraint *fc_con = (Constraint *) lfirst(fc_clist);

		if (!IsA(fc_con, Constraint))
			elog(ERROR, "unrecognized node type: %d",
				 (int) nodeTag(fc_con));
		switch (fc_con->contype)
		{
			case CONSTR_ATTR_DEFERRABLE:
				if (!SUPPORTS_ATTRS(fc_lastprimarycon))
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("misplaced DEFERRABLE clause"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
				if (fc_saw_deferrability)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
				fc_saw_deferrability = true;
				fc_lastprimarycon->deferrable = true;
				break;

			case CONSTR_ATTR_NOT_DEFERRABLE:
				if (!SUPPORTS_ATTRS(fc_lastprimarycon))
#ifdef FDD //cppcheck
				{
					//cppcheck
#endif
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("misplaced NOT DEFERRABLE clause"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
#ifdef FDD //cppcheck
					return;
				}
#endif
				if (fc_saw_deferrability)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
				fc_saw_deferrability = true;
				fc_lastprimarycon->deferrable = false;
				if (fc_saw_initially &&
					fc_lastprimarycon->initdeferred)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
				break;

			case CONSTR_ATTR_DEFERRED:
				if (!SUPPORTS_ATTRS(fc_lastprimarycon))
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("misplaced INITIALLY DEFERRED clause"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
				if (fc_saw_initially)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
				fc_saw_initially = true;
				fc_lastprimarycon->initdeferred = true;

				/*
				 * 如果只出现 INITIALLY DEFERRED，假设为 DEFERRABLE
				 */
				if (!fc_saw_deferrability)
					fc_lastprimarycon->deferrable = true;
				else if (!fc_lastprimarycon->deferrable)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
				break;

			case CONSTR_ATTR_IMMEDIATE:
				if (!SUPPORTS_ATTRS(fc_lastprimarycon))
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("misplaced INITIALLY IMMEDIATE clause"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
				if (fc_saw_initially)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"),
							 parser_errposition(fc_cxt->pstate, fc_con->location)));
				fc_saw_initially = true;
				fc_lastprimarycon->initdeferred = false;
				break;

			default:
				/* 否则它不是一个属性 */
				fc_lastprimarycon = fc_con;
				/* 为新的主节点重置标志 */
				fc_saw_deferrability = false;
				fc_saw_initially = false;
				break;
		}
	}
}

/*
 * 对列的类型定义进行特殊处理
 */
static void fc_transformColumnType(CreateStmtContext *fc_cxt, ColumnDef *fc_column)
{
	/*
	 * 我们在这里真正需要做的只是验证类型是否有效，包括可能存在的任何排序规范。
	 */
	Type		fc_ctype = typenameType(fc_cxt->pstate, fc_column->typeName, NULL);

	if (fc_column->collClause)
	{
		Form_pg_type fc_typtup = (Form_pg_type) GETSTRUCT(fc_ctype);

		LookupCollation(fc_cxt->pstate,
						fc_column->collClause->collname,
						fc_column->collClause->location);
		/* 如果 COLLATE 应用于不可排序类型，则进行抱怨 */
		if (!OidIsValid(fc_typtup->typcollation))
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("collations are not supported by type %s",
							format_type_be(fc_typtup->oid)),
					 parser_errposition(fc_cxt->pstate,
										fc_column->collClause->location)));
	}

	ReleaseSysCache(fc_ctype);
}


/*
 * transformCreateSchemaStmtElements -
 *	  分析 CREATE SCHEMA 语句的元素
 *
 * 将 CREATE SCHEMA 语句中的 schema 元素列表拆分为单个命令，并将它们按顺序放置在结果列表中，以便没有前向引用（例如，授予对列表中稍后创建的表的访问权限）。请注意，我们用于确定前向引用的逻辑目前相当不完整。
 *
 * "schemaName" 是将用于创建列出的对象的模式的名称，该名称可以从语句中定义的模式名或角色规范中编译而来。
 *
 * SQL 还允许约束进行前向引用，因此浏览表列并将前向引用移动到后续的 ALTER TABLE 命令。
 *
 * 结果是一个仍需分析的解析节点列表——但在执行之前的命令之前我们无法分析后面的命令，因为可能存在对象之间的引用。
 *
 * 注意：这在一定程度上打破了规则，因为它修改了传入结构中的 schema-name 字段。然而，如果重新执行，转换将是相同的，因此在输入上进行这种程度的涂鸦应该是可以的。
 */
List * transformCreateSchemaStmtElements(List *fc_schemaElts, const char *fc_schemaName)
{
	CreateSchemaStmtContext fc_cxt;
	List	   *fc_result;
	ListCell   *fc_elements;

	fc_cxt.schemaname = fc_schemaName;
	fc_cxt.sequences = NIL;
	fc_cxt.tables = NIL;
	fc_cxt.views = NIL;
	fc_cxt.indexes = NIL;
	fc_cxt.triggers = NIL;
	fc_cxt.grants = NIL;

	/*
	 * 遍历 schema 元素列表中的每个 schema 元素。按类型分离语句，并进行初步分析。
	 */
	foreach(fc_elements, fc_schemaElts)
	{
		Node	   *fc_element = lfirst(fc_elements);

		switch (nodeTag(fc_element))
		{
			case T_CreateSeqStmt:
				{
					CreateSeqStmt *fc_elp = (CreateSeqStmt *) fc_element;

					fc_setSchemaName(fc_cxt.schemaname, &fc_elp->sequence->schemaname);
					fc_cxt.sequences = lappend(fc_cxt.sequences, fc_element);
				}
				break;

			case T_CreateStmt:
				{
					CreateStmt *fc_elp = (CreateStmt *) fc_element;

					fc_setSchemaName(fc_cxt.schemaname, &fc_elp->relation->schemaname);

					/*
					 * XXX 待办事项：处理约束
					 */
					fc_cxt.tables = lappend(fc_cxt.tables, fc_element);
				}
				break;

			case T_ViewStmt:
				{
					ViewStmt   *fc_elp = (ViewStmt *) fc_element;

					fc_setSchemaName(fc_cxt.schemaname, &fc_elp->view->schemaname);

					/*
					 * XXX 待办事项：处理视图之间的引用
					 */
					fc_cxt.views = lappend(fc_cxt.views, fc_element);
				}
				break;

			case T_IndexStmt:
				{
					IndexStmt  *fc_elp = (IndexStmt *) fc_element;

					fc_setSchemaName(fc_cxt.schemaname, &fc_elp->relation->schemaname);
					fc_cxt.indexes = lappend(fc_cxt.indexes, fc_element);
				}
				break;

			case T_CreateTrigStmt:
				{
					CreateTrigStmt *fc_elp = (CreateTrigStmt *) fc_element;

					fc_setSchemaName(fc_cxt.schemaname, &fc_elp->relation->schemaname);
					fc_cxt.triggers = lappend(fc_cxt.triggers, fc_element);
				}
				break;

			case T_GrantStmt:
				fc_cxt.grants = lappend(fc_cxt.grants, fc_element);
				break;

			default:
				elog(ERROR, "unrecognized node type: %d",
					 (int) nodeTag(fc_element));
		}
	}

	fc_result = NIL;
	fc_result = list_concat(fc_result, fc_cxt.sequences);
	fc_result = list_concat(fc_result, fc_cxt.tables);
	fc_result = list_concat(fc_result, fc_cxt.views);
	fc_result = list_concat(fc_result, fc_cxt.indexes);
	fc_result = list_concat(fc_result, fc_cxt.triggers);
	fc_result = list_concat(fc_result, fc_cxt.grants);

	return fc_result;
}

/*
 * setSchemaName
 *		在 CREATE SCHEMA 命令的一个元素中设置或检查模式名称
 */
static void fc_setSchemaName(const char *fc_context_schema, char **fc_stmt_schema_name)
{
	if (*fc_stmt_schema_name == NULL)
		*fc_stmt_schema_name = unconstify(char *, fc_context_schema);
	else if (strcmp(fc_context_schema, *fc_stmt_schema_name) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
				 errmsg("CREATE specifies a schema (%s) "
						"different from the one being created (%s)",
						*fc_stmt_schema_name, fc_context_schema)));
}

/*
 * transformPartitionCmd
 *		分析 ATTACH/DETACH PARTITION 命令
 *
 * 在 ATTACH PARTITION 命令的情况下，cxt->partbound 被设置为 cmd->bound 的转换值。
 */
static void fc_transformPartitionCmd(CreateStmtContext *fc_cxt, PartitionCmd *fc_cmd)
{
	Relation	fc_parentRel = fc_cxt->rel;

	switch (fc_parentRel->rd_rel->relkind)
	{
		case RELKIND_PARTITIONED_TABLE:
			/* 如果有，转换分区边界 */
			Assert(RelationGetPartitionKey(fc_parentRel) != NULL);
			if (fc_cmd->bound != NULL)
				fc_cxt->partbound = transformPartitionBound(fc_cxt->pstate, fc_parentRel,
														 fc_cmd->bound);
			break;
		case RELKIND_PARTITIONED_INDEX:

			/*
			 * 分区索引不能设置分区边界。ALTER INDEX 通过其语法防止了这一点，但 ALTER TABLE 并不如此。
			 */
			if (fc_cmd->bound != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("\"%s\" is not a partitioned table",
								RelationGetRelationName(fc_parentRel))));
			break;
		case RELKIND_RELATION:
			/* 表必须是分区的 */
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("table \"%s\" is not partitioned",
							RelationGetRelationName(fc_parentRel))));
			break;
		case RELKIND_INDEX:
			/* 索引必须是分区的 */
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("index \"%s\" is not partitioned",
							RelationGetRelationName(fc_parentRel))));
			break;
		default:
			/* 解析器不应该让这种情况通过 */
			elog(ERROR, "\"%s\" is not a partitioned table or index",
				 RelationGetRelationName(fc_parentRel));
			break;
	}
}

/*
 * transformPartitionBound
 *
 * 转换分区边界规范
 */
PartitionBoundSpec *
transformPartitionBound(ParseState *fc_pstate, Relation fc_parent,
						PartitionBoundSpec *fc_spec)
{
	PartitionBoundSpec *fc_result_spec;
	PartitionKey fc_key = RelationGetPartitionKey(fc_parent);
	char		fc_strategy = get_partition_strategy(fc_key);
	int			fc_partnatts = get_partition_natts(fc_key);
	List	   *fc_partexprs = get_partition_exprs(fc_key);

	/* 避免在输入上涂鸦 */
	fc_result_spec = copyObject(fc_spec);

	if (fc_spec->is_default)
	{
		/*
		 * 哈希分区不支持默认分区；没有用例（因为要创建的分区集是完全定义的），如果用户偶然进入，这是很难事后撤出的。
		 */
		if (fc_strategy == PARTITION_STRATEGY_HASH)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("a hash-partitioned table may not have a default partition")));

		/*
		 * 在默认分区的情况下，解析器无法识别分区策略。将父级的策略分配给默认分区边界规范。
		 */
		fc_result_spec->strategy = fc_strategy;

		return fc_result_spec;
	}

	if (fc_strategy == PARTITION_STRATEGY_HASH)
	{
		if (fc_spec->strategy != PARTITION_STRATEGY_HASH)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("invalid bound specification for a hash partition"),
					 parser_errposition(fc_pstate, exprLocation((Node *) fc_spec))));

		if (fc_spec->modulus <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("modulus for hash partition must be an integer value greater than zero")));

		Assert(fc_spec->remainder >= 0);

		if (fc_spec->remainder >= fc_spec->modulus)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("remainder for hash partition must be less than modulus")));
	}
	else if (fc_strategy == PARTITION_STRATEGY_LIST)
	{
		ListCell   *fc_cell;
		char	   *fc_colname;
		Oid			fc_coltype;
		int32		fc_coltypmod;
		Oid			fc_partcollation;

		if (fc_spec->strategy != PARTITION_STRATEGY_LIST)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("invalid bound specification for a list partition"),
					 parser_errposition(fc_pstate, exprLocation((Node *) fc_spec))));

		/* 在需要输出错误时获取唯一列的名称 */
		if (fc_key->partattrs[0] != 0)
			fc_colname = get_attname(RelationGetRelid(fc_parent),
								  fc_key->partattrs[0], false);
		else
			fc_colname = deparse_expression((Node *) linitial(fc_partexprs),
										 deparse_context_for(RelationGetRelationName(fc_parent),
															 RelationGetRelid(fc_parent)),
										 false, false);
		/* 还需要它的类型数据 */
		fc_coltype = get_partition_col_typid(fc_key, 0);
		fc_coltypmod = get_partition_col_typmod(fc_key, 0);
		fc_partcollation = get_partition_col_collation(fc_key, 0);

		fc_result_spec->listdatums = NIL;
		foreach(fc_cell, fc_spec->listdatums)
		{
			Node	   *fc_expr = lfirst(fc_cell);
			Const	   *fc_value;
			ListCell   *fc_cell2;
			bool		fc_duplicate;

			fc_value = fc_transformPartitionBoundValue(fc_pstate, fc_expr,
												 fc_colname, fc_coltype, fc_coltypmod,
												 fc_partcollation);

			/* 不要在结果中添加重复的值 */
			fc_duplicate = false;
			foreach(fc_cell2, fc_result_spec->listdatums)
			{
				Const	   *fc_value2 = lfirst_node(Const, fc_cell2);

				if (equal(fc_value, fc_value2))
				{
					fc_duplicate = true;
					break;
				}
			}
			if (fc_duplicate)
				continue;

			fc_result_spec->listdatums = lappend(fc_result_spec->listdatums,
											  fc_value);
		}
	}
	else if (fc_strategy == PARTITION_STRATEGY_RANGE)
	{
		if (fc_spec->strategy != PARTITION_STRATEGY_RANGE)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("invalid bound specification for a range partition"),
					 parser_errposition(fc_pstate, exprLocation((Node *) fc_spec))));

		if (list_length(fc_spec->lowerdatums) != fc_partnatts)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("FROM must specify exactly one value per partitioning column")));
		if (list_length(fc_spec->upperdatums) != fc_partnatts)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("TO must specify exactly one value per partitioning column")));

		/*
		 * 将原始解析节点转换为 PartitionRangeDatum 节点，并执行
		 * 任何必要的验证。
		 */
		fc_result_spec->lowerdatums =
			fc_transformPartitionRangeBounds(fc_pstate, fc_spec->lowerdatums,
										  fc_parent);
		fc_result_spec->upperdatums =
			fc_transformPartitionRangeBounds(fc_pstate, fc_spec->upperdatums,
										  fc_parent);
	}
	else
		elog(ERROR, "unexpected partition strategy: %d", (int) fc_strategy);

	return fc_result_spec;
}

/*
 * transformPartitionRangeBounds
 *		这将范围分区边界的表达式从原始
 *		语法表示转换为 PartitionRangeDatum 结构体
 */
static List * fc_transformPartitionRangeBounds(ParseState *fc_pstate, List *fc_blist,
							  Relation fc_parent)
{
	List	   *fc_result = NIL;
	PartitionKey fc_key = RelationGetPartitionKey(fc_parent);
	List	   *fc_partexprs = get_partition_exprs(fc_key);
	ListCell   *fc_lc;
	int			fc_i,
				fc_j;

	fc_i = fc_j = 0;
	foreach(fc_lc, fc_blist)
	{
		Node	   *fc_expr = lfirst(fc_lc);
		PartitionRangeDatum *fc_prd = NULL;

		/*
		 * 无限范围边界 -- “minvalue”和“maxvalue” -- 作为 ColumnRefs 传入。
		 */
		if (IsA(fc_expr, ColumnRef))
		{
			ColumnRef  *fc_cref = (ColumnRef *) fc_expr;
			char	   *fc_cname = NULL;

			/*
			 * 应该有一个名为“minvalue”或
			 * “maxvalue”的单一字段。
			 */
			if (list_length(fc_cref->fields) == 1 &&
				IsA(linitial(fc_cref->fields), String))
				fc_cname = strVal(linitial(fc_cref->fields));

			if (fc_cname == NULL)
			{
				/*
				 * ColumnRef 不符合期望的单字段名称形式。为了
				 * 在所有分区策略之间保持一致，让
				 * 表达式转换报告任何错误，而不是
				 * 我们自己处理。
				 */
			}
			else if (strcmp("minvalue", fc_cname) == 0)
			{
				fc_prd = makeNode(PartitionRangeDatum);
				fc_prd->kind = PARTITION_RANGE_DATUM_MINVALUE;
				fc_prd->value = NULL;
			}
			else if (strcmp("maxvalue", fc_cname) == 0)
			{
				fc_prd = makeNode(PartitionRangeDatum);
				fc_prd->kind = PARTITION_RANGE_DATUM_MAXVALUE;
				fc_prd->value = NULL;
			}
		}

		if (fc_prd == NULL)
		{
			char	   *fc_colname;
			Oid			fc_coltype;
			int32		fc_coltypmod;
			Oid			fc_partcollation;
			Const	   *fc_value;

			/* 获取列的名称，以便在需要输出错误时使用 */
			if (fc_key->partattrs[fc_i] != 0)
				fc_colname = get_attname(RelationGetRelid(fc_parent),
									  fc_key->partattrs[fc_i], false);
			else
			{
				fc_colname = deparse_expression((Node *) list_nth(fc_partexprs, fc_j),
											 deparse_context_for(RelationGetRelationName(fc_parent),
																 RelationGetRelid(fc_parent)),
											 false, false);
				++fc_j;
			}

			/* 还需要它的类型数据 */
			fc_coltype = get_partition_col_typid(fc_key, fc_i);
			fc_coltypmod = get_partition_col_typmod(fc_key, fc_i);
			fc_partcollation = get_partition_col_collation(fc_key, fc_i);

			fc_value = fc_transformPartitionBoundValue(fc_pstate, fc_expr,
												 fc_colname,
												 fc_coltype, fc_coltypmod,
												 fc_partcollation);
			if (fc_value->constisnull)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("cannot specify NULL in range bound")));
			fc_prd = makeNode(PartitionRangeDatum);
			fc_prd->kind = PARTITION_RANGE_DATUM_VALUE;
			fc_prd->value = (Node *) fc_value;
			++fc_i;
		}

		fc_prd->location = exprLocation(fc_expr);

		fc_result = lappend(fc_result, fc_prd);
	}

	/*
	 * 一旦我们看到某列的 MINVALUE 或 MAXVALUE，剩余的列
	 * 必须相同。
	 */
	fc_validateInfiniteBounds(fc_pstate, fc_result);

	return fc_result;
}

/*
 * validateInfiniteBounds
 *
 * 检查分区边界中的 MAXVALUE 或 MINVALUE 规范后面是否
 * 仅跟随相同的指定。
 */
static void fc_validateInfiniteBounds(ParseState *fc_pstate, List *fc_blist)
{
	ListCell   *fc_lc;
	PartitionRangeDatumKind fc_kind = PARTITION_RANGE_DATUM_VALUE;

	foreach(fc_lc, fc_blist)
	{
		PartitionRangeDatum *fc_prd = lfirst_node(PartitionRangeDatum, fc_lc);

		if (fc_kind == fc_prd->kind)
			continue;

		switch (fc_kind)
		{
			case PARTITION_RANGE_DATUM_VALUE:
				fc_kind = fc_prd->kind;
				break;

			case PARTITION_RANGE_DATUM_MAXVALUE:
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("every bound following MAXVALUE must also be MAXVALUE"),
						 parser_errposition(fc_pstate, exprLocation((Node *) fc_prd))));
				break;

			case PARTITION_RANGE_DATUM_MINVALUE:
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("every bound following MINVALUE must also be MINVALUE"),
						 parser_errposition(fc_pstate, exprLocation((Node *) fc_prd))));
				break;
		}
	}
}

/*
 * 转换分区边界规范中的一个条目，生成一个常量。
 */
static Const * fc_transformPartitionBoundValue(ParseState *fc_pstate, Node *fc_val,
							 const char *fc_colName, Oid fc_colType, int32 fc_colTypmod,
							 Oid fc_partCollation)
{
	Node	   *fc_value;

	/* 转换原始解析树 */
	fc_value = transformExpr(fc_pstate, fc_val, EXPR_KIND_PARTITION_BOUND);

	/*
	 * transformExpr() 应该已经拒绝了列引用、
	 * 子查询、聚合、窗口函数和 SRFs，基于
	 * 分区边界表达式的 EXPR_KIND_。
	 */
	Assert(!contain_var_clause(fc_value));

	/*
	 * 强制转换为正确的类型。这可能会在表达式上添加
	 * 一个显式的强制转换步骤，该步骤必须在
	 * 返回结果给调用者之前进行评估。
	 */
	fc_value = coerce_to_target_type(fc_pstate,
								  fc_value, exprType(fc_value),
								  fc_colType,
								  fc_colTypmod,
								  COERCION_ASSIGNMENT,
								  COERCE_IMPLICIT_CAST,
								  -1);

	if (fc_value == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("specified value cannot be cast to type %s for column \"%s\"",
						format_type_be(fc_colType), fc_colName),
				 parser_errposition(fc_pstate, exprLocation(fc_val))));

	/*
	 * 如果需要，评估表达式，将分区键的数据
	 * 类型和排序规则分配给结果的 Const 节点。
	 */
	if (!IsA(fc_value, Const))
	{
		assign_expr_collations(fc_pstate, fc_value);
		fc_value = (Node *) expression_planner((Expr *) fc_value);
		fc_value = (Node *) evaluate_expr((Expr *) fc_value, fc_colType, fc_colTypmod,
									   fc_partCollation);
		if (!IsA(fc_value, Const))
			elog(ERROR, "could not evaluate partition bound expression");
	}
	else
	{
		/*
		 * 如果表达式已经是一个 Const，通常情况如此，我们可以
		 * 跳过上面的相当昂贵的步骤。但我们仍然必须插入
		 * 正确的排序规则，因为 coerce_to_target_type 不处理
		 * 这个问题。
		 */
		((Const *) fc_value)->constcollid = fc_partCollation;
	}

	/*
	 * 将原始表达式的解析位置附加到 Const，这样
	 * 任何与此分区边界相关的后续错误都会报告这个位置。
	 */
	((Const *) fc_value)->location = exprLocation(fc_val);

	return (Const *) fc_value;
}
